@lumy-pack/scene-sieve 0.0.4 → 0.0.5
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.mjs +571 -246
- package/dist/commands/Sieve.d.ts +13 -0
- package/dist/components/PhaseStep.d.ts +14 -0
- package/dist/components/ProgressBar.d.ts +7 -0
- package/dist/constants.d.ts +1 -0
- package/dist/core/extractor.d.ts +2 -2
- package/dist/core/pipeline-worker.d.ts +1 -0
- package/dist/core/run-in-worker.d.ts +9 -0
- package/dist/core/workspace.d.ts +5 -0
- package/dist/index.cjs +14 -30
- package/dist/index.mjs +15 -31
- package/dist/pipeline-worker.mjs +1049 -0
- package/dist/types/index.d.ts +2 -0
- package/package.json +5 -4
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface SieveViewProps {
|
|
3
|
+
input: string;
|
|
4
|
+
count?: number;
|
|
5
|
+
threshold?: number;
|
|
6
|
+
output?: string;
|
|
7
|
+
fps: number;
|
|
8
|
+
maxFrames: number;
|
|
9
|
+
scale: number;
|
|
10
|
+
quality: number;
|
|
11
|
+
debug: boolean;
|
|
12
|
+
}
|
|
13
|
+
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 {};
|
package/dist/constants.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export declare const DEFAULT_THRESHOLD = 0.5;
|
|
|
4
4
|
export declare const DEFAULT_FPS = 5;
|
|
5
5
|
export declare const DEFAULT_SCALE = 720;
|
|
6
6
|
export declare const DEFAULT_QUALITY = 80;
|
|
7
|
+
export declare const DEFAULT_MAX_FRAMES = 300;
|
|
7
8
|
export declare const NORMALIZATION_PERCENTILE = 0.9;
|
|
8
9
|
export declare const WORKSPACE_PREFIX = "scene-sieve-";
|
|
9
10
|
export declare const TEMP_BASE_DIR: string;
|
package/dist/core/extractor.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { FrameNode, ProcessContext } from '../types/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Extract frames from video/GIF using FFmpeg.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Always uses FPS-based extraction. For long videos, FPS is automatically
|
|
5
|
+
* reduced to stay within maxFrames budget.
|
|
6
6
|
*/
|
|
7
7
|
export declare function extractFrames(ctx: ProcessContext): Promise<FrameNode[]>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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>;
|
package/dist/core/workspace.d.ts
CHANGED
|
@@ -2,6 +2,11 @@ import type { FrameNode, ProcessContext } from '../types/index.js';
|
|
|
2
2
|
export declare function createWorkspace(sessionId: string): Promise<string>;
|
|
3
3
|
export declare function finalizeOutput(ctx: ProcessContext, selectedFrames: FrameNode[]): Promise<string[]>;
|
|
4
4
|
export declare function cleanupWorkspace(workspacePath: string): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Remove stale workspace directories left by previous interrupted runs.
|
|
7
|
+
* Only deletes directories older than 1 hour to avoid removing active workspaces.
|
|
8
|
+
*/
|
|
9
|
+
export declare function cleanupStaleWorkspaces(): Promise<void>;
|
|
5
10
|
/**
|
|
6
11
|
* Write a video buffer to a temp file in the workspace and return the path.
|
|
7
12
|
* Used by 'buffer' input mode.
|
package/dist/index.cjs
CHANGED
|
@@ -84,6 +84,7 @@ var DEFAULT_THRESHOLD = 0.5;
|
|
|
84
84
|
var DEFAULT_FPS = 5;
|
|
85
85
|
var DEFAULT_SCALE = 720;
|
|
86
86
|
var DEFAULT_QUALITY = 80;
|
|
87
|
+
var DEFAULT_MAX_FRAMES = 300;
|
|
87
88
|
var NORMALIZATION_PERCENTILE = 0.9;
|
|
88
89
|
var WORKSPACE_PREFIX = `${APP_NAME}-`;
|
|
89
90
|
var TEMP_BASE_DIR = (0, import_node_os.tmpdir)();
|
|
@@ -97,7 +98,6 @@ var SUPPORTED_VIDEO_EXTENSIONS = [
|
|
|
97
98
|
var SUPPORTED_GIF_EXTENSIONS = [".gif"];
|
|
98
99
|
var FRAME_OUTPUT_EXTENSION = ".jpg";
|
|
99
100
|
var OPENCV_BATCH_SIZE = 10;
|
|
100
|
-
var MIN_IFRAME_COUNT = 3;
|
|
101
101
|
var DBSCAN_ALPHA = 0.03;
|
|
102
102
|
var DBSCAN_MIN_PTS = 4;
|
|
103
103
|
var IOU_THRESHOLD = 0.9;
|
|
@@ -577,7 +577,7 @@ function isSupportedFile(filePath, extensions) {
|
|
|
577
577
|
// src/core/extractor.ts
|
|
578
578
|
async function extractFrames(ctx) {
|
|
579
579
|
const framesDir = (0, import_node_path3.join)(ctx.workspacePath, "frames");
|
|
580
|
-
const { inputPath, fps, scale } = ctx.options;
|
|
580
|
+
const { inputPath, fps, maxFrames, scale } = ctx.options;
|
|
581
581
|
if (!inputPath) {
|
|
582
582
|
throw new Error("inputPath is required for frame extraction");
|
|
583
583
|
}
|
|
@@ -594,39 +594,21 @@ async function extractFrames(ctx) {
|
|
|
594
594
|
}
|
|
595
595
|
logger.debug(`Extracting frames from: ${inputPath}`);
|
|
596
596
|
await ensureDir(framesDir);
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
if (
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
`Insufficient I-frames (${frames.length}), falling back to FPS mode`
|
|
607
|
-
);
|
|
608
|
-
frames = await extractByFps(inputPath, framesDir, fps, scale);
|
|
609
|
-
}
|
|
597
|
+
let effectiveFps = fps;
|
|
598
|
+
const duration = await getVideoDuration(inputPath).catch(() => 0);
|
|
599
|
+
if (duration > 0) {
|
|
600
|
+
const fpsCap = maxFrames / duration;
|
|
601
|
+
effectiveFps = Math.min(fps, fpsCap);
|
|
602
|
+
effectiveFps = Math.max(0.5, effectiveFps);
|
|
603
|
+
logger.debug(
|
|
604
|
+
`Duration: ${duration.toFixed(1)}s, FPS: ${fps} \u2192 effective: ${effectiveFps.toFixed(2)} (maxFrames: ${maxFrames})`
|
|
605
|
+
);
|
|
610
606
|
}
|
|
607
|
+
const frames = await extractByFps(inputPath, framesDir, effectiveFps, scale);
|
|
611
608
|
ctx.emitProgress(100);
|
|
612
609
|
logger.debug(`Extracted ${frames.length} frames`);
|
|
613
610
|
return frames;
|
|
614
611
|
}
|
|
615
|
-
async function extractIFrames(inputPath, outputDir, scale) {
|
|
616
|
-
const outputPattern = (0, import_node_path3.join)(outputDir, "frame_%06d.jpg");
|
|
617
|
-
await (0, import_execa.execa)(import_ffmpeg_static.default, [
|
|
618
|
-
"-i",
|
|
619
|
-
inputPath,
|
|
620
|
-
"-vf",
|
|
621
|
-
`select='eq(pict_type,I)',scale=-1:${scale}`,
|
|
622
|
-
"-vsync",
|
|
623
|
-
"vfr",
|
|
624
|
-
"-q:v",
|
|
625
|
-
"2",
|
|
626
|
-
outputPattern
|
|
627
|
-
]);
|
|
628
|
-
return buildFrameList(outputDir, inputPath);
|
|
629
|
-
}
|
|
630
612
|
async function extractByFps(inputPath, outputDir, fps, scale) {
|
|
631
613
|
const outputPattern = (0, import_node_path3.join)(outputDir, "frame_%06d.jpg");
|
|
632
614
|
await (0, import_execa.execa)(import_ffmpeg_static.default, [
|
|
@@ -710,6 +692,7 @@ async function cleanupWorkspace(workspacePath) {
|
|
|
710
692
|
} catch {
|
|
711
693
|
}
|
|
712
694
|
}
|
|
695
|
+
var STALE_THRESHOLD_MS = 60 * 60 * 1e3;
|
|
713
696
|
async function writeInputBuffer(buffer, workspacePath) {
|
|
714
697
|
const inputDir = (0, import_node_path4.join)(workspacePath, "input");
|
|
715
698
|
await ensureDir(inputDir);
|
|
@@ -757,6 +740,7 @@ function resolveOptions(options) {
|
|
|
757
740
|
pruneMode,
|
|
758
741
|
outputPath,
|
|
759
742
|
fps: options.fps ?? DEFAULT_FPS,
|
|
743
|
+
maxFrames: options.maxFrames ?? DEFAULT_MAX_FRAMES,
|
|
760
744
|
scale: options.scale ?? DEFAULT_SCALE,
|
|
761
745
|
quality: options.quality ?? DEFAULT_QUALITY,
|
|
762
746
|
debug: options.debug ?? false
|
package/dist/index.mjs
CHANGED
|
@@ -44,6 +44,7 @@ var DEFAULT_THRESHOLD = 0.5;
|
|
|
44
44
|
var DEFAULT_FPS = 5;
|
|
45
45
|
var DEFAULT_SCALE = 720;
|
|
46
46
|
var DEFAULT_QUALITY = 80;
|
|
47
|
+
var DEFAULT_MAX_FRAMES = 300;
|
|
47
48
|
var NORMALIZATION_PERCENTILE = 0.9;
|
|
48
49
|
var WORKSPACE_PREFIX = `${APP_NAME}-`;
|
|
49
50
|
var TEMP_BASE_DIR = tmpdir();
|
|
@@ -57,7 +58,6 @@ var SUPPORTED_VIDEO_EXTENSIONS = [
|
|
|
57
58
|
var SUPPORTED_GIF_EXTENSIONS = [".gif"];
|
|
58
59
|
var FRAME_OUTPUT_EXTENSION = ".jpg";
|
|
59
60
|
var OPENCV_BATCH_SIZE = 10;
|
|
60
|
-
var MIN_IFRAME_COUNT = 3;
|
|
61
61
|
var DBSCAN_ALPHA = 0.03;
|
|
62
62
|
var DBSCAN_MIN_PTS = 4;
|
|
63
63
|
var IOU_THRESHOLD = 0.9;
|
|
@@ -537,7 +537,7 @@ function isSupportedFile(filePath, extensions) {
|
|
|
537
537
|
// src/core/extractor.ts
|
|
538
538
|
async function extractFrames(ctx) {
|
|
539
539
|
const framesDir = join2(ctx.workspacePath, "frames");
|
|
540
|
-
const { inputPath, fps, scale } = ctx.options;
|
|
540
|
+
const { inputPath, fps, maxFrames, scale } = ctx.options;
|
|
541
541
|
if (!inputPath) {
|
|
542
542
|
throw new Error("inputPath is required for frame extraction");
|
|
543
543
|
}
|
|
@@ -554,39 +554,21 @@ async function extractFrames(ctx) {
|
|
|
554
554
|
}
|
|
555
555
|
logger.debug(`Extracting frames from: ${inputPath}`);
|
|
556
556
|
await ensureDir(framesDir);
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
if (
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
`Insufficient I-frames (${frames.length}), falling back to FPS mode`
|
|
567
|
-
);
|
|
568
|
-
frames = await extractByFps(inputPath, framesDir, fps, scale);
|
|
569
|
-
}
|
|
557
|
+
let effectiveFps = fps;
|
|
558
|
+
const duration = await getVideoDuration(inputPath).catch(() => 0);
|
|
559
|
+
if (duration > 0) {
|
|
560
|
+
const fpsCap = maxFrames / duration;
|
|
561
|
+
effectiveFps = Math.min(fps, fpsCap);
|
|
562
|
+
effectiveFps = Math.max(0.5, effectiveFps);
|
|
563
|
+
logger.debug(
|
|
564
|
+
`Duration: ${duration.toFixed(1)}s, FPS: ${fps} \u2192 effective: ${effectiveFps.toFixed(2)} (maxFrames: ${maxFrames})`
|
|
565
|
+
);
|
|
570
566
|
}
|
|
567
|
+
const frames = await extractByFps(inputPath, framesDir, effectiveFps, scale);
|
|
571
568
|
ctx.emitProgress(100);
|
|
572
569
|
logger.debug(`Extracted ${frames.length} frames`);
|
|
573
570
|
return frames;
|
|
574
571
|
}
|
|
575
|
-
async function extractIFrames(inputPath, outputDir, scale) {
|
|
576
|
-
const outputPattern = join2(outputDir, "frame_%06d.jpg");
|
|
577
|
-
await execa(ffmpegPath, [
|
|
578
|
-
"-i",
|
|
579
|
-
inputPath,
|
|
580
|
-
"-vf",
|
|
581
|
-
`select='eq(pict_type,I)',scale=-1:${scale}`,
|
|
582
|
-
"-vsync",
|
|
583
|
-
"vfr",
|
|
584
|
-
"-q:v",
|
|
585
|
-
"2",
|
|
586
|
-
outputPattern
|
|
587
|
-
]);
|
|
588
|
-
return buildFrameList(outputDir, inputPath);
|
|
589
|
-
}
|
|
590
572
|
async function extractByFps(inputPath, outputDir, fps, scale) {
|
|
591
573
|
const outputPattern = join2(outputDir, "frame_%06d.jpg");
|
|
592
574
|
await execa(ffmpegPath, [
|
|
@@ -637,7 +619,7 @@ async function buildFrameList(framesDir, inputPath) {
|
|
|
637
619
|
import { join as join4 } from "path";
|
|
638
620
|
|
|
639
621
|
// src/core/workspace.ts
|
|
640
|
-
import { rename, rm, writeFile } from "fs/promises";
|
|
622
|
+
import { readdir as readdir2, rename, rm, stat as stat2, writeFile } from "fs/promises";
|
|
641
623
|
import { join as join3 } from "path";
|
|
642
624
|
import sharp2 from "sharp";
|
|
643
625
|
async function createWorkspace(sessionId) {
|
|
@@ -670,6 +652,7 @@ async function cleanupWorkspace(workspacePath) {
|
|
|
670
652
|
} catch {
|
|
671
653
|
}
|
|
672
654
|
}
|
|
655
|
+
var STALE_THRESHOLD_MS = 60 * 60 * 1e3;
|
|
673
656
|
async function writeInputBuffer(buffer, workspacePath) {
|
|
674
657
|
const inputDir = join3(workspacePath, "input");
|
|
675
658
|
await ensureDir(inputDir);
|
|
@@ -717,6 +700,7 @@ function resolveOptions(options) {
|
|
|
717
700
|
pruneMode,
|
|
718
701
|
outputPath,
|
|
719
702
|
fps: options.fps ?? DEFAULT_FPS,
|
|
703
|
+
maxFrames: options.maxFrames ?? DEFAULT_MAX_FRAMES,
|
|
720
704
|
scale: options.scale ?? DEFAULT_SCALE,
|
|
721
705
|
quality: options.quality ?? DEFAULT_QUALITY,
|
|
722
706
|
debug: options.debug ?? false
|