@lumy-pack/scene-sieve 0.1.0 → 0.2.1
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/README.md +128 -42
- package/dist/{commands → cli/commands}/Sieve.d.ts +5 -0
- package/dist/{errors.d.ts → cli/errors/classify-error.d.ts} +5 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/{utils → cli/options}/parse-options.d.ts +11 -2
- package/dist/cli.mjs +1453 -505
- package/dist/constants/package-version.d.ts +2 -0
- package/dist/constants/pipeline-defaults.d.ts +33 -0
- package/dist/core/{analyzer.d.ts → analyzer/analyzer.d.ts} +11 -6
- package/dist/core/{dbscan.d.ts → analyzer/clustering/dbscan.d.ts} +1 -1
- package/dist/core/analyzer/constants/vision-tuning.d.ts +12 -0
- package/dist/core/analyzer/features/feature-diff.d.ts +14 -0
- package/dist/core/analyzer/features/frame-features.d.ts +32 -0
- package/dist/core/analyzer/index.d.ts +3 -0
- package/dist/core/constants/workspace-layout.d.ts +9 -0
- package/dist/core/{extractor.d.ts → extractor/extractor.d.ts} +6 -4
- package/dist/core/extractor/index.d.ts +1 -0
- package/dist/core/index.d.ts +9 -9
- package/dist/core/input-resolver/index.d.ts +1 -0
- package/dist/core/{input-resolver.d.ts → input-resolver/input-resolver.d.ts} +11 -1
- package/dist/core/input-resolver/validation/validate-options.d.ts +8 -0
- package/dist/core/orchestrator/index.d.ts +3 -0
- package/dist/core/orchestrator/orchestrator.d.ts +8 -0
- package/dist/core/{run-in-worker.d.ts → orchestrator/worker/run-in-worker.d.ts} +5 -1
- package/dist/core/pruner/index.d.ts +1 -0
- package/dist/core/{pruner.d.ts → pruner/pruner.d.ts} +2 -2
- package/dist/{utils/math.d.ts → core/pruner/scoring/normalize-scores.d.ts} +4 -0
- package/dist/core/segmenter/index.d.ts +1 -0
- package/dist/core/{segmenter.d.ts → segmenter/segmenter.d.ts} +11 -11
- package/dist/core/utils/metadata/build-edge-metadata.d.ts +11 -0
- package/dist/core/utils/metadata/build-frame-metadata.d.ts +20 -0
- package/dist/core/utils/metadata/build-sieve-metadata.d.ts +14 -0
- package/dist/core/utils/metadata/build-tool-metadata.d.ts +8 -0
- package/dist/core/utils/metadata/build-video-metadata.d.ts +14 -0
- package/dist/core/utils/metadata/change/build-frame-change.d.ts +20 -0
- package/dist/core/utils/metadata/change/select-regions.d.ts +8 -0
- package/dist/core/utils/metadata/change/union-area/y-coverage-tree.d.ts +26 -0
- package/dist/core/utils/metadata/change/union-area.d.ts +7 -0
- package/dist/core/utils/metadata/scale-bounding-box.d.ts +11 -0
- package/dist/core/utils/output/finalize-selection.d.ts +15 -0
- package/dist/core/utils/sheet/build-tile-label-svg.d.ts +8 -0
- package/dist/core/utils/sheet/format-tile-label.d.ts +7 -0
- package/dist/core/utils/sheet/render-contact-sheet.d.ts +19 -0
- package/dist/core/utils/sheet/sample-tile-frames.d.ts +10 -0
- package/dist/core/workspace/index.d.ts +1 -0
- package/dist/core/{workspace.d.ts → workspace/workspace.d.ts} +11 -2
- package/dist/index.cjs +1531 -559
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +1533 -558
- package/dist/pipeline-worker.mjs +1213 -404
- package/dist/types/index.d.ts +173 -0
- package/package.json +14 -11
- package/dist/constants.d.ts +0 -32
- package/dist/core/orchestrator.d.ts +0 -2
- /package/dist/{utils → cli/commands}/command-registry.d.ts +0 -0
- /package/dist/{components → cli/components}/PhaseStep.d.ts +0 -0
- /package/dist/{components → cli/components}/ProgressBar.d.ts +0 -0
- /package/dist/core/{pipeline-worker.d.ts → orchestrator/worker/pipeline-worker.d.ts} +0 -0
- /package/dist/{utils → core/pruner/heap}/min-heap.d.ts +0 -0
- /package/dist/{utils → core/segmenter/scheduling}/concurrency.d.ts +0 -0
- /package/dist/{utils → core/utils/filesystem}/paths.d.ts +0 -0
- /package/dist/{utils → logging}/logger.d.ts +0 -0
package/README.md
CHANGED
|
@@ -8,17 +8,17 @@ Automatically extract the most meaningful frames from video and GIF files using
|
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
Video/GIF ──▶ Extract (FFmpeg) ──▶ Analyze (OpenCV) ──▶ Prune ──▶ Output
|
|
11
|
-
|
|
11
|
+
FPS grid AKAZE + DBSCAN MinHeap JPG / Buffer
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
## Features
|
|
15
15
|
|
|
16
16
|
- **Animation Tracking** — Detects and records loading spinners or other repetitive animations
|
|
17
|
-
- **
|
|
17
|
+
- **Change signals and contact sheets** — Metadata v2 records regions, raw scores and holds, with an optional `sheet.jpg` overview
|
|
18
18
|
- **Smart frame selection** — Identifies visually significant scene changes, not just evenly-spaced samples
|
|
19
19
|
- **Computer vision pipeline** — AKAZE feature detection, DBSCAN clustering, IoU tracking, and information gain scoring
|
|
20
20
|
- **Three input modes** — File path, video Buffer, or pre-extracted frame Buffers
|
|
21
|
-
- **Flexible pruning** —
|
|
21
|
+
- **Flexible pruning** — Filter by a distribution-normalized threshold, then apply a count cap
|
|
22
22
|
- **Bundled FFmpeg** — No system-level FFmpeg installation required
|
|
23
23
|
- **Dual output** — ESM and CommonJS compatible
|
|
24
24
|
- **Progress callbacks** — Track extraction progress in real time
|
|
@@ -40,7 +40,7 @@ yarn add @lumy-pack/scene-sieve
|
|
|
40
40
|
# Extract 20 key scenes (default)
|
|
41
41
|
npx scene-sieve input.mp4
|
|
42
42
|
|
|
43
|
-
# Keep
|
|
43
|
+
# Keep up to 8 scenes
|
|
44
44
|
npx scene-sieve input.mp4 -n 8
|
|
45
45
|
|
|
46
46
|
# Use threshold-based selection
|
|
@@ -84,13 +84,15 @@ scene-sieve <input> [options]
|
|
|
84
84
|
| `-n, --count <number>` | Max number of frames to keep | `20` |
|
|
85
85
|
| `-t, --threshold <number>` | Normalized score threshold (0, 1] | `0.5` |
|
|
86
86
|
| `-o, --output <path>` | Output directory | Same directory as input |
|
|
87
|
-
| `--fps <number>` |
|
|
88
|
-
| `-mf, --max-frames <number>` |
|
|
89
|
-
| `-s, --scale <number>` |
|
|
87
|
+
| `--fps <number>` | Requested extraction FPS (positive decimals allowed) | `5` |
|
|
88
|
+
| `-mf, --max-frames <number>` | Strict candidate cap (auto-reduces FPS) | `300` |
|
|
89
|
+
| `-s, --scale <number>` | Extraction height / analysis width (px) | `720` |
|
|
90
90
|
| `-q, --quality <number>` | JPEG output quality (1–100) | `80` |
|
|
91
91
|
| `-it, --iou-threshold <number>`| IoU threshold for animation tracking (0–1) | `0.9` |
|
|
92
92
|
| `-at, --anim-threshold <number>`| Min consecutive frames for animation | `5` |
|
|
93
93
|
| `--debug` | Preserve temp workspace for inspection | `false` |
|
|
94
|
+
| `--sheet` | Generate a selected-frame contact sheet | `false` |
|
|
95
|
+
| `--include-edges` | Include candidate edge diagnostics in metadata | `false` |
|
|
94
96
|
|
|
95
97
|
### Supported Formats
|
|
96
98
|
|
|
@@ -114,7 +116,7 @@ Not sure where to start? Here's how each parameter affects the output, based on
|
|
|
114
116
|
|
|
115
117
|
#### `--threshold` — Minimum score to keep a frame
|
|
116
118
|
|
|
117
|
-
Higher values
|
|
119
|
+
Higher values mean stricter filtering and fewer frames. Scores are normalized against the positive-score distribution within the video: min-max for up to 10 positive scores, otherwise a blend of median/MAD-based logistic scores and percentile ranks. `0.5` is neither an absolute change level nor half the maximum score.
|
|
118
120
|
|
|
119
121
|
| Setting | Selected | Notes |
|
|
120
122
|
|---------|----------|-------|
|
|
@@ -124,24 +126,27 @@ Higher values = stricter filtering = fewer frames.
|
|
|
124
126
|
| `-t 0.7` | 19 | Starts filtering subtle changes |
|
|
125
127
|
| `-t 0.9` | 12 | Only major scene transitions survive |
|
|
126
128
|
|
|
127
|
-
> **Tip**:
|
|
129
|
+
> **Tip**: Using `-t` alone still applies the default `count=20` cap. Adjust it with `-n` (e.g., `-t 0.3 -n 10`).
|
|
128
130
|
|
|
129
131
|
#### `--fps` and `--max-frames` — Extraction density
|
|
130
132
|
|
|
131
133
|
These control how many frames are pulled from the video before analysis. More frames = more precision but longer processing.
|
|
132
134
|
|
|
135
|
+
File/buffer candidates obey the strict `maxFrames` cap (integer ≥ 2). Effective FPS is `min(fps, maxFrames / duration)`, with no 0.5 FPS floor. Positive decimals such as `--fps 0.5` are accepted. A one-hour video with a 300-frame budget is sampled at about 0.083 FPS (every 12 seconds). This cap does not apply to frames input.
|
|
136
|
+
|
|
137
|
+
Timestamps describe the FFmpeg output grid `k / effectiveFps`, rather than the original source frame PTS. The FPS filter keeps its default rounding. When the budget binds, the last grid point is `duration - duration / maxFrames`, leaving no candidate in the remaining interval to the end.
|
|
138
|
+
|
|
133
139
|
| Setting | Extracted | Selected | Time |
|
|
134
140
|
|---------|-----------|----------|------|
|
|
135
141
|
| `--fps 1` | 18 | 6 | ~5s |
|
|
136
142
|
| `--fps 5` (default) | 90 | 20 | ~25s |
|
|
137
143
|
| `--fps 10` | 180 | 20 | ~47s |
|
|
138
|
-
| `-mf 50` | 47 | 13 | ~13s |
|
|
139
144
|
|
|
140
145
|
> **Tip**: For quick previews, `--fps 1` is 5x faster. For frame-accurate analysis, `--fps 10` captures finer transitions.
|
|
141
146
|
|
|
142
147
|
#### `--scale` — Analysis resolution
|
|
143
148
|
|
|
144
|
-
|
|
149
|
+
File/buffer extraction scales height to `scale`, while analysis preprocessing scales width to `scale`. Final JPEGs retain extraction dimensions; frames input retains its input dimensions in the output. Lower values are faster but less sensitive.
|
|
145
150
|
|
|
146
151
|
| Setting | Selected | Time | Output Size |
|
|
147
152
|
|---------|----------|------|-------------|
|
|
@@ -283,12 +288,16 @@ interface SieveOptionsBase {
|
|
|
283
288
|
count?: number; // Max frames to keep (default: 20)
|
|
284
289
|
threshold?: number; // Score threshold in range (0, 1] (default: 0.5)
|
|
285
290
|
outputPath?: string; // Output directory (file mode only)
|
|
286
|
-
fps?: number; //
|
|
287
|
-
maxFrames?: number; //
|
|
288
|
-
scale?: number; //
|
|
291
|
+
fps?: number; // Requested FPS, positive decimals allowed (default: 5)
|
|
292
|
+
maxFrames?: number; // Strict file/buffer candidate cap (default: 300)
|
|
293
|
+
scale?: number; // Extraction height / analysis width in px (default: 720)
|
|
289
294
|
quality?: number; // JPEG quality 1-100 (default: 80)
|
|
290
295
|
iouThreshold?: number; // IoU for animation tracking (default: 0.9)
|
|
291
296
|
animationThreshold?: number; // Min frames for animation (default: 5)
|
|
297
|
+
maxSegmentDuration?: number; // Segment duration in seconds (default: 300)
|
|
298
|
+
concurrency?: number; // Parallel segment workers (default: 2)
|
|
299
|
+
sheet?: boolean | SheetOptions; // Contact sheet (default: false)
|
|
300
|
+
includeEdges?: boolean; // Candidate edge diagnostics (default: false)
|
|
292
301
|
debug?: boolean; // Preserve temp workspace (default: false)
|
|
293
302
|
onProgress?: (phase: ProgressPhase, percent: number) => void;
|
|
294
303
|
}
|
|
@@ -296,6 +305,22 @@ interface SieveOptionsBase {
|
|
|
296
305
|
type SieveOptions = SieveOptionsBase & SieveInput;
|
|
297
306
|
```
|
|
298
307
|
|
|
308
|
+
Supplied numeric options are validated before defaults; invalid values produce `INVALID_INPUT`. CLI numeric strings are checked in full, so values such as `5abc` are rejected. Both interactive and JSON CLI failures exit with code 1.
|
|
309
|
+
|
|
310
|
+
| Option | Allowed range |
|
|
311
|
+
| --- | --- |
|
|
312
|
+
| `count`, `animationThreshold`, `concurrency` | Integer ≥ 1 |
|
|
313
|
+
| `maxFrames` | Integer ≥ 2 |
|
|
314
|
+
| `scale` | Integer ≥ 16 |
|
|
315
|
+
| `quality` | Integer 1–100 |
|
|
316
|
+
| `fps`, `maxSegmentDuration` | Finite positive number (decimals allowed) |
|
|
317
|
+
| `threshold` | Finite (0, 1] |
|
|
318
|
+
| `iouThreshold` | Finite [0, 1] |
|
|
319
|
+
| `sheet` | Boolean or `SheetOptions`: columns integer ≥ 1, tileWidth integer ≥ 16, maxTiles integer ≥ 2, label boolean. Default false; enabled defaults 4, 320, 40, true |
|
|
320
|
+
| `includeEdges` | Boolean, default false |
|
|
321
|
+
|
|
322
|
+
All images in frames input must share the same width and height. Empty arrays and single images are accepted.
|
|
323
|
+
|
|
299
324
|
### Result
|
|
300
325
|
|
|
301
326
|
```typescript
|
|
@@ -307,19 +332,20 @@ interface SieveResult {
|
|
|
307
332
|
outputBuffers?: Buffer[]; // JPEG buffers (buffer/frames mode)
|
|
308
333
|
animations?: AnimationMetadata[]; // Detected animations
|
|
309
334
|
video?: VideoMetadata; // Video source metadata
|
|
335
|
+
frames?: FrameMetadata[]; // Same one-based frames as the metadata document
|
|
336
|
+
sheet?: SheetMetadata; // Present only when a sheet was rendered
|
|
337
|
+
sheetBuffer?: Buffer; // JPEG sheet in buffer/frames modes only
|
|
310
338
|
executionTimeMs: number;
|
|
311
339
|
}
|
|
312
340
|
```
|
|
313
341
|
|
|
342
|
+
`frames[i]` pairs with `outputBuffers[i]`; fileName remains the same deterministic file-mode label for in-memory output. API frames[].frameId is one-based, while API animations[].startFrameId and endFrameId remain zero-based. Document animation IDs are one-based.
|
|
343
|
+
|
|
314
344
|
### Pruning Strategies
|
|
315
345
|
|
|
316
|
-
The
|
|
346
|
+
The pipeline always uses **threshold-with-cap**: filter by distribution-normalized score, then prune to the `count` cap. Omitted `threshold` and `count` default to `0.5` and `20`. The selected count may be lower depending on candidates and scores.
|
|
317
347
|
|
|
318
|
-
|
|
319
|
-
| -------------------------- | ---------------------- | ---------------------------------------------------------------- |
|
|
320
|
-
| `count` only | **count** | Greedy merge — removes lowest-scored frames until `count` remain |
|
|
321
|
-
| `threshold` only | **threshold** | Keeps frames with normalized score >= `threshold` |
|
|
322
|
-
| Both `count` + `threshold` | **threshold-with-cap** | Applies threshold filter first, then caps at `count` |
|
|
348
|
+
The first and last candidates are protected, so both remain when `count=1` and at least two candidates exist. Standalone count and threshold strategies exist as internal functions but are not selected by the CLI or `extractScenes`.
|
|
323
349
|
|
|
324
350
|
### Progress Tracking
|
|
325
351
|
|
|
@@ -337,32 +363,92 @@ const result = await extractScenes({
|
|
|
337
363
|
|
|
338
364
|
## Output Metadata
|
|
339
365
|
|
|
340
|
-
|
|
366
|
+
File mode writes a v2 `.metadata.json` beside the selected JPEGs. Existing fields retain their meaning; treat a document without `metadataVersion` as v1. The package exports `SieveMetadata`, `FrameMetadata`, `FrameChange`, `EdgeMetadata`, `EdgeChange`, `ToolMetadata`, `ToolParams`, `SheetMetadata`, `SheetOptions`, `VideoMetadata` and `AnimationMetadata`.
|
|
367
|
+
|
|
368
|
+
`tool.name` and `tool.version` identify the runtime package. `tool.params` contains exactly nine fields in the order shown below. Its `fps` is the requested value; `video.fps` is the effective value. Cache selected frames using input identity, tool version and these params. Include sheet settings and `includeEdges` when caching complete output bundles, because those output options are excluded from params.
|
|
369
|
+
|
|
370
|
+
`SieveResult.video` and the document share these values:
|
|
371
|
+
|
|
372
|
+
- `originalDurationMs`: ffprobe duration for file/buffer, or the last candidate timestamp for frames input.
|
|
373
|
+
- `fps`: effective sampling frequency; frames input and its animation tracker use 1.
|
|
374
|
+
- `resolution`: actual output JPEG size of the first selection, falling back to the first candidate or 0×0.
|
|
375
|
+
- `candidatesCount` and `selectedCount`: counts before and after pruning.
|
|
376
|
+
- `source`: input mode and basename only; `fileName` is null for buffer/frames input.
|
|
377
|
+
|
|
378
|
+
Each `frames[]` entry has a one-based step and candidate ID, a deterministic `fileName`, rounded `timestampMs`, and `holdsMs`: time until the next selected frame, or until the source end for the last frame, clamped to zero. Frames input uses one-second candidate intervals.
|
|
379
|
+
|
|
380
|
+
The first frame has `change: null`. Later entries aggregate every available adjacent candidate edge from the previous selection to this one:
|
|
381
|
+
|
|
382
|
+
| Field | Meaning |
|
|
383
|
+
| --- | --- |
|
|
384
|
+
| `fromFrameId` | Previous selected candidate ID, one-based |
|
|
385
|
+
| `skippedCandidates` | Pruned candidates between the selections |
|
|
386
|
+
| `peakScore`, `sumScore` | Maximum and sum of raw G(t), before pruning normalization |
|
|
387
|
+
| `areaRatio` | Exact union area of all non-animation cluster boxes divided by analysis image area, clamped to [0,1] |
|
|
388
|
+
| `regions` | Up to five distinct largest boxes in integer output pixels, sorted by area descending, then y, x and width ascending |
|
|
389
|
+
|
|
390
|
+
Area is computed in analysis coordinates before output rounding. It is an absolute box-area fraction, distinct from both feature-density G(t) and video-relative normalized pruning scores. Overlapping boxes contribute only once. Region boxes use the same axis-specific scaling, rounding and clamping as `animations[].boundingBox`.
|
|
391
|
+
|
|
392
|
+
Animation exclusion follows `animationIndices`: exactly the clusters the tracker classified as animation and discounted in G(t) for that pair. Earlier observations can remain in change regions before the tracker recognizes repetition; there is no retrospective removal based on the final animation list. Failed pairs contribute their fallback score without boxes.
|
|
393
|
+
|
|
394
|
+
`includeEdges: true` (CLI `--include-edges`) appends `edges[]` in candidate graph order with `sourceFrameId`, `targetFrameId` (one-based), raw `score`, `areaRatio` and `animatedAreaRatio`. The key is absent by default.
|
|
395
|
+
|
|
396
|
+
Use `sheet: true` or CLI `--sheet` for `sheet.jpg`. API defaults are `{ columns: 4, tileWidth: 320, maxTiles: 40, label: true }`; partial objects override individual fields. Tiles preserve the output aspect ratio with a white background and 4px gaps/margins. Labels read `#<frameId> mm:ss.s` with tenths truncated. Excess tiles are sampled uniformly, always including the first and last selection. `sheet` records the effective columns, tile dimensions, one-based tile `frameIds` and `sampled`. File output order is frame JPEGs, optional sheet, then metadata. Buffer/frames mode returns `sheetBuffer`; absent or empty sheets create neither `sheet` nor `sheetBuffer` keys.
|
|
397
|
+
|
|
398
|
+
Metadata keys have a fixed order. Area ratios round to four decimals; raw scores round to six. Trailing zeroes are not preserved by JSON. Identical input bytes, basename, tool version, params and output options produce identical metadata bytes regardless of concurrency, output directory or execution time. Sheet JPEG determinism is limited to the same machine, sharp version and font environment.
|
|
399
|
+
|
|
400
|
+
The following complete example was generated from a four-second FFmpeg `testsrc=size=320x240:rate=5` MP4 using:
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
scene-sieve test_input.mp4 --fps 5 -mf 12 -n 2 -t 0.001 -s 320 --sheet
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
The runtime version is 0.2.0 before the minor changeset is released; these values come from an actual run.
|
|
341
407
|
|
|
342
408
|
```json
|
|
343
409
|
{
|
|
410
|
+
"metadataVersion": 2,
|
|
411
|
+
"tool": {
|
|
412
|
+
"name": "@lumy-pack/scene-sieve",
|
|
413
|
+
"version": "0.2.0",
|
|
414
|
+
"params": {
|
|
415
|
+
"fps": 5, "count": 2, "threshold": 0.001, "scale": 320, "quality": 80,
|
|
416
|
+
"maxFrames": 12, "iouThreshold": 0.9, "animationThreshold": 5,
|
|
417
|
+
"maxSegmentDuration": 300
|
|
418
|
+
}
|
|
419
|
+
},
|
|
344
420
|
"video": {
|
|
345
|
-
"originalDurationMs":
|
|
346
|
-
"
|
|
347
|
-
"
|
|
421
|
+
"originalDurationMs": 4000, "fps": 3,
|
|
422
|
+
"resolution": { "width": 427, "height": 320 },
|
|
423
|
+
"candidatesCount": 12, "selectedCount": 2,
|
|
424
|
+
"source": { "fileName": "test_input.mp4", "mode": "file" }
|
|
348
425
|
},
|
|
349
426
|
"frames": [
|
|
350
427
|
{
|
|
351
|
-
"step": 1,
|
|
352
|
-
"
|
|
353
|
-
|
|
354
|
-
"timestampMs": 0
|
|
355
|
-
}
|
|
356
|
-
],
|
|
357
|
-
"animations": [
|
|
428
|
+
"step": 1, "fileName": "frame_0001.jpg", "frameId": 1,
|
|
429
|
+
"timestampMs": 0, "holdsMs": 3667, "change": null
|
|
430
|
+
},
|
|
358
431
|
{
|
|
359
|
-
"
|
|
360
|
-
"
|
|
361
|
-
"
|
|
362
|
-
|
|
363
|
-
|
|
432
|
+
"step": 2, "fileName": "frame_0012.jpg", "frameId": 12,
|
|
433
|
+
"timestampMs": 3667, "holdsMs": 333,
|
|
434
|
+
"change": {
|
|
435
|
+
"fromFrameId": 1, "skippedCandidates": 10,
|
|
436
|
+
"peakScore": 0.000833, "sumScore": 0.006784, "areaRatio": 0.1186,
|
|
437
|
+
"regions": [
|
|
438
|
+
{ "x": 340, "y": 124, "width": 32, "height": 69 },
|
|
439
|
+
{ "x": 32, "y": 240, "width": 64, "height": 32 },
|
|
440
|
+
{ "x": 64, "y": 240, "width": 64, "height": 32 },
|
|
441
|
+
{ "x": 113, "y": 240, "width": 64, "height": 32 },
|
|
442
|
+
{ "x": 145, "y": 240, "width": 64, "height": 32 }
|
|
443
|
+
]
|
|
444
|
+
}
|
|
364
445
|
}
|
|
365
|
-
]
|
|
446
|
+
],
|
|
447
|
+
"animations": [],
|
|
448
|
+
"sheet": {
|
|
449
|
+
"fileName": "sheet.jpg", "columns": 2, "tileWidth": 320, "tileHeight": 240,
|
|
450
|
+
"frameIds": [1, 12], "sampled": false
|
|
451
|
+
}
|
|
366
452
|
}
|
|
367
453
|
```
|
|
368
454
|
|
|
@@ -373,16 +459,16 @@ When running in `file` mode, `scene-sieve` generates a `.metadata.json` file in
|
|
|
373
459
|
scene-sieve processes input through a 5-stage pipeline:
|
|
374
460
|
|
|
375
461
|
1. **Init** — Creates a temporary workspace and resolves input mode
|
|
376
|
-
2. **Extract** — Pulls
|
|
462
|
+
2. **Extract** — Pulls candidates on an FFmpeg FPS grid within the strict `maxFrames` cap (skipped in `frames` mode)
|
|
377
463
|
3. **Analyze** — Computes an information gain score G(t) for each adjacent frame pair
|
|
378
|
-
4. **Prune** — Selects frames
|
|
379
|
-
5. **Finalize** —
|
|
464
|
+
4. **Prune** — Selects frames from G(t) scores using threshold-with-cap
|
|
465
|
+
5. **Finalize** — Deletes existing output before renaming staging, or returns Buffers; cleans up workspace
|
|
380
466
|
|
|
381
467
|
### Vision Analysis
|
|
382
468
|
|
|
383
469
|
The analyzer scores each pair of adjacent frames through 4 stages:
|
|
384
470
|
|
|
385
|
-
1. **AKAZE Feature Diff** —
|
|
471
|
+
1. **AKAZE Feature Diff** — Reuses per-frame preprocessing/features and the detector to compute newly appeared points (sNew only)
|
|
386
472
|
2. **DBSCAN Clustering** — Groups new feature points into spatial clusters
|
|
387
473
|
3. **IoU Tracking** — Tracks cluster bounding boxes across time; identifies and records repeated animation regions (e.g. loading spinners)
|
|
388
474
|
4. **G(t) Scoring** — Calculates information gain from cluster area ratio and feature density, discounting animated areas to focus on unique scene content
|
|
@@ -391,7 +477,7 @@ Frames with higher G(t) scores represent greater visual change and are preserved
|
|
|
391
477
|
|
|
392
478
|
## Requirements
|
|
393
479
|
|
|
394
|
-
- **Node.js** >=
|
|
480
|
+
- **Node.js** 20.19+ (20.x) or >= 22.12
|
|
395
481
|
- **FFmpeg**: Bundled via `ffmpeg-static` — no system installation needed
|
|
396
482
|
- **OpenCV**: Bundled as WASM via `@techstark/opencv-js` — no native build needed
|
|
397
483
|
- **sharp**: Requires native binaries. Pre-built binaries are automatically downloaded for most platforms. See the [sharp installation guide](https://sharp.pixelplumbing.com/install) if you encounter build issues.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import React from 'react';
|
|
3
|
+
import type { SheetOptions } from '../../types/index.js';
|
|
3
4
|
export interface SieveViewProps {
|
|
4
5
|
input: string;
|
|
5
6
|
count?: number;
|
|
@@ -14,6 +15,10 @@ export interface SieveViewProps {
|
|
|
14
15
|
maxSegmentDuration?: number;
|
|
15
16
|
concurrency?: number;
|
|
16
17
|
debug: boolean;
|
|
18
|
+
/** Contact sheet settings forwarded to the pipeline. */
|
|
19
|
+
sheet?: boolean | SheetOptions;
|
|
20
|
+
/** Whether to include candidate edge diagnostics. */
|
|
21
|
+
includeEdges?: boolean;
|
|
17
22
|
}
|
|
18
23
|
export declare function registerSieveCommand(program: Command, version: string): void;
|
|
19
24
|
export declare const SieveView: React.FC<SieveViewProps>;
|
|
@@ -7,4 +7,9 @@ export declare const SieveErrorCode: {
|
|
|
7
7
|
readonly UNKNOWN: "UNKNOWN";
|
|
8
8
|
};
|
|
9
9
|
export type SieveErrorCode = (typeof SieveErrorCode)[keyof typeof SieveErrorCode];
|
|
10
|
+
/**
|
|
11
|
+
* Classify pipeline errors for structured CLI responses.
|
|
12
|
+
* @param error - Failure with a diagnostic message and optional filesystem code.
|
|
13
|
+
* @returns The existing error code matching the failure.
|
|
14
|
+
*/
|
|
10
15
|
export declare function classifyError(error: Error): SieveErrorCode;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { SieveOptionsBase } from '
|
|
1
|
+
import type { SieveOptionsBase } from '../../types/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Common pipeline options parsed from CLI opts (Commander string values → typed values).
|
|
4
4
|
* Does NOT include mode-specific fields (mode, inputPath, onProgress).
|
|
5
5
|
*/
|
|
6
|
-
export type ParsedPipelineOptions = Pick<Required<SieveOptionsBase>, 'fps' | 'maxFrames' | 'scale' | 'quality' | 'debug'> & Pick<SieveOptionsBase, 'count' | 'threshold' | 'outputPath' | 'iouThreshold' | 'animationThreshold' | 'maxSegmentDuration' | 'concurrency'>;
|
|
6
|
+
export type ParsedPipelineOptions = Pick<Required<SieveOptionsBase>, 'fps' | 'maxFrames' | 'scale' | 'quality' | 'debug'> & Pick<SieveOptionsBase, 'count' | 'threshold' | 'outputPath' | 'iouThreshold' | 'animationThreshold' | 'maxSegmentDuration' | 'concurrency' | 'sheet' | 'includeEdges'>;
|
|
7
7
|
export interface RawCliOptions {
|
|
8
8
|
count?: string;
|
|
9
9
|
threshold?: string;
|
|
@@ -17,5 +17,14 @@ export interface RawCliOptions {
|
|
|
17
17
|
maxSegmentDuration?: string;
|
|
18
18
|
concurrency?: string;
|
|
19
19
|
debug?: boolean;
|
|
20
|
+
/** Request a contact sheet with default settings. */
|
|
21
|
+
sheet?: boolean;
|
|
22
|
+
/** Request candidate edge diagnostics. */
|
|
23
|
+
includeEdges?: boolean;
|
|
20
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Convert CLI strings to pipeline values for subsequent range validation.
|
|
27
|
+
* @param opts - Commander options with raw numeric strings.
|
|
28
|
+
* @returns Typed settings, preserving invalid numeric input as NaN.
|
|
29
|
+
*/
|
|
21
30
|
export declare function parsePipelineOptions(opts: RawCliOptions): ParsedPipelineOptions;
|