@lumy-pack/scene-sieve 0.2.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 +88 -25
- package/dist/cli/commands/Sieve.d.ts +5 -0
- package/dist/cli/options/parse-options.d.ts +5 -1
- package/dist/cli.mjs +749 -237
- package/dist/constants/package-version.d.ts +2 -0
- package/dist/constants/pipeline-defaults.d.ts +20 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/orchestrator/orchestrator.d.ts +6 -0
- 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/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/workspace.d.ts +11 -2
- package/dist/index.cjs +701 -205
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +700 -204
- package/dist/pipeline-worker.mjs +640 -188
- package/dist/types/index.d.ts +148 -0
- package/package.json +14 -11
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Video/GIF ──▶ Extract (FFmpeg) ──▶ Analyze (OpenCV) ──▶ Prune
|
|
|
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
|
|
@@ -91,6 +91,8 @@ scene-sieve <input> [options]
|
|
|
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
|
|
|
@@ -294,6 +296,8 @@ interface SieveOptionsBase {
|
|
|
294
296
|
animationThreshold?: number; // Min frames for animation (default: 5)
|
|
295
297
|
maxSegmentDuration?: number; // Segment duration in seconds (default: 300)
|
|
296
298
|
concurrency?: number; // Parallel segment workers (default: 2)
|
|
299
|
+
sheet?: boolean | SheetOptions; // Contact sheet (default: false)
|
|
300
|
+
includeEdges?: boolean; // Candidate edge diagnostics (default: false)
|
|
297
301
|
debug?: boolean; // Preserve temp workspace (default: false)
|
|
298
302
|
onProgress?: (phase: ProgressPhase, percent: number) => void;
|
|
299
303
|
}
|
|
@@ -312,6 +316,8 @@ Supplied numeric options are validated before defaults; invalid values produce `
|
|
|
312
316
|
| `fps`, `maxSegmentDuration` | Finite positive number (decimals allowed) |
|
|
313
317
|
| `threshold` | Finite (0, 1] |
|
|
314
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 |
|
|
315
321
|
|
|
316
322
|
All images in frames input must share the same width and height. Empty arrays and single images are accepted.
|
|
317
323
|
|
|
@@ -326,10 +332,15 @@ interface SieveResult {
|
|
|
326
332
|
outputBuffers?: Buffer[]; // JPEG buffers (buffer/frames mode)
|
|
327
333
|
animations?: AnimationMetadata[]; // Detected animations
|
|
328
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
|
|
329
338
|
executionTimeMs: number;
|
|
330
339
|
}
|
|
331
340
|
```
|
|
332
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
|
+
|
|
333
344
|
### Pruning Strategies
|
|
334
345
|
|
|
335
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.
|
|
@@ -352,40 +363,92 @@ const result = await extractScenes({
|
|
|
352
363
|
|
|
353
364
|
## Output Metadata
|
|
354
365
|
|
|
355
|
-
|
|
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.
|
|
356
395
|
|
|
357
|
-
`
|
|
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.
|
|
358
397
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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.
|
|
364
407
|
|
|
365
408
|
```json
|
|
366
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
|
+
},
|
|
367
420
|
"video": {
|
|
368
|
-
"originalDurationMs":
|
|
369
|
-
"
|
|
370
|
-
"
|
|
421
|
+
"originalDurationMs": 4000, "fps": 3,
|
|
422
|
+
"resolution": { "width": 427, "height": 320 },
|
|
423
|
+
"candidatesCount": 12, "selectedCount": 2,
|
|
424
|
+
"source": { "fileName": "test_input.mp4", "mode": "file" }
|
|
371
425
|
},
|
|
372
426
|
"frames": [
|
|
373
427
|
{
|
|
374
|
-
"step": 1,
|
|
375
|
-
"
|
|
376
|
-
|
|
377
|
-
"timestampMs": 0
|
|
378
|
-
}
|
|
379
|
-
],
|
|
380
|
-
"animations": [
|
|
428
|
+
"step": 1, "fileName": "frame_0001.jpg", "frameId": 1,
|
|
429
|
+
"timestampMs": 0, "holdsMs": 3667, "change": null
|
|
430
|
+
},
|
|
381
431
|
{
|
|
382
|
-
"
|
|
383
|
-
"
|
|
384
|
-
"
|
|
385
|
-
|
|
386
|
-
|
|
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
|
+
}
|
|
387
445
|
}
|
|
388
|
-
]
|
|
446
|
+
],
|
|
447
|
+
"animations": [],
|
|
448
|
+
"sheet": {
|
|
449
|
+
"fileName": "sheet.jpg", "columns": 2, "tileWidth": 320, "tileHeight": 240,
|
|
450
|
+
"frameIds": [1, 12], "sampled": false
|
|
451
|
+
}
|
|
389
452
|
}
|
|
390
453
|
```
|
|
391
454
|
|
|
@@ -414,7 +477,7 @@ Frames with higher G(t) scores represent greater visual change and are preserved
|
|
|
414
477
|
|
|
415
478
|
## Requirements
|
|
416
479
|
|
|
417
|
-
- **Node.js** >=
|
|
480
|
+
- **Node.js** 20.19+ (20.x) or >= 22.12
|
|
418
481
|
- **FFmpeg**: Bundled via `ffmpeg-static` — no system installation needed
|
|
419
482
|
- **OpenCV**: Bundled as WASM via `@techstark/opencv-js` — no native build needed
|
|
420
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>;
|
|
@@ -3,7 +3,7 @@ import type { SieveOptionsBase } from '../../types/index.js';
|
|
|
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,6 +17,10 @@ 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
|
}
|
|
21
25
|
/**
|
|
22
26
|
* Convert CLI strings to pipeline values for subsequent range validation.
|