@doedja/scenecut 1.0.0
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 +280 -0
- package/bin/cli.js +293 -0
- package/dist/decoder/ffmpeg-decoder.d.ts +50 -0
- package/dist/decoder/ffmpeg-decoder.d.ts.map +1 -0
- package/dist/decoder/ffmpeg-decoder.js +269 -0
- package/dist/decoder/ffmpeg-decoder.js.map +1 -0
- package/dist/decoder/frame-buffer.d.ts +81 -0
- package/dist/decoder/frame-buffer.d.ts.map +1 -0
- package/dist/decoder/frame-buffer.js +123 -0
- package/dist/decoder/frame-buffer.js.map +1 -0
- package/dist/detection/detector.d.ts +19 -0
- package/dist/detection/detector.d.ts.map +1 -0
- package/dist/detection/detector.js +126 -0
- package/dist/detection/detector.js.map +1 -0
- package/dist/detection/wasm-bridge.d.ts +82 -0
- package/dist/detection/wasm-bridge.d.ts.map +1 -0
- package/dist/detection/wasm-bridge.js +182 -0
- package/dist/detection/wasm-bridge.js.map +1 -0
- package/dist/detection.wasm.js +2 -0
- package/dist/detection.wasm.wasm +0 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/keyframes.cjs.js +985 -0
- package/dist/keyframes.cjs.js.map +1 -0
- package/dist/keyframes.esm.js +946 -0
- package/dist/keyframes.esm.js.map +1 -0
- package/dist/types/index.d.ts +225 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/buffer-pool.d.ts +44 -0
- package/dist/utils/buffer-pool.d.ts.map +1 -0
- package/dist/utils/buffer-pool.js +81 -0
- package/dist/utils/buffer-pool.js.map +1 -0
- package/dist/utils/frame-processor.d.ts +48 -0
- package/dist/utils/frame-processor.d.ts.map +1 -0
- package/dist/utils/frame-processor.js +112 -0
- package/dist/utils/frame-processor.js.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FFmpeg Decoder - Extract frames from video files
|
|
3
|
+
*
|
|
4
|
+
* Uses fluent-ffmpeg to extract grayscale frames for scene detection
|
|
5
|
+
*/
|
|
6
|
+
import * as ffmpeg from 'fluent-ffmpeg';
|
|
7
|
+
import * as ffmpegInstaller from '@ffmpeg-installer/ffmpeg';
|
|
8
|
+
import { FrameBuffer } from './frame-buffer';
|
|
9
|
+
// Set FFmpeg path from installer
|
|
10
|
+
ffmpeg.setFfmpegPath(ffmpegInstaller.path);
|
|
11
|
+
/**
|
|
12
|
+
* Ring Buffer - Fixed-size circular buffer for streaming data
|
|
13
|
+
* Eliminates repeated Buffer.concat() allocations and GC pressure
|
|
14
|
+
*/
|
|
15
|
+
class RingBuffer {
|
|
16
|
+
constructor(size = 8 * 1024 * 1024) {
|
|
17
|
+
this.writePos = 0;
|
|
18
|
+
this.readPos = 0;
|
|
19
|
+
this.availableBytes = 0;
|
|
20
|
+
this.buffer = Buffer.allocUnsafe(size);
|
|
21
|
+
this.capacity = size;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Write data to the ring buffer
|
|
25
|
+
*/
|
|
26
|
+
write(chunk) {
|
|
27
|
+
const chunkSize = chunk.length;
|
|
28
|
+
if (chunkSize > this.capacity - this.availableBytes) {
|
|
29
|
+
throw new Error('RingBuffer overflow: chunk too large for available space');
|
|
30
|
+
}
|
|
31
|
+
// Write in two parts if wrapping around
|
|
32
|
+
const endSpace = this.capacity - this.writePos;
|
|
33
|
+
if (chunkSize <= endSpace) {
|
|
34
|
+
// No wrap-around needed
|
|
35
|
+
chunk.copy(this.buffer, this.writePos);
|
|
36
|
+
this.writePos += chunkSize;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// Wrap-around: split write
|
|
40
|
+
chunk.copy(this.buffer, this.writePos, 0, endSpace);
|
|
41
|
+
chunk.copy(this.buffer, 0, endSpace, chunkSize);
|
|
42
|
+
this.writePos = chunkSize - endSpace;
|
|
43
|
+
}
|
|
44
|
+
// Wrap write position if at end
|
|
45
|
+
if (this.writePos >= this.capacity) {
|
|
46
|
+
this.writePos = 0;
|
|
47
|
+
}
|
|
48
|
+
this.availableBytes += chunkSize;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Read data from the ring buffer
|
|
52
|
+
*/
|
|
53
|
+
read(size) {
|
|
54
|
+
if (size > this.availableBytes) {
|
|
55
|
+
throw new Error('RingBuffer underflow: not enough data available');
|
|
56
|
+
}
|
|
57
|
+
const result = Buffer.allocUnsafe(size);
|
|
58
|
+
const endSpace = this.capacity - this.readPos;
|
|
59
|
+
if (size <= endSpace) {
|
|
60
|
+
// No wrap-around needed
|
|
61
|
+
this.buffer.copy(result, 0, this.readPos, this.readPos + size);
|
|
62
|
+
this.readPos += size;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Wrap-around: split read
|
|
66
|
+
this.buffer.copy(result, 0, this.readPos, this.capacity);
|
|
67
|
+
this.buffer.copy(result, endSpace, 0, size - endSpace);
|
|
68
|
+
this.readPos = size - endSpace;
|
|
69
|
+
}
|
|
70
|
+
// Wrap read position if at end
|
|
71
|
+
if (this.readPos >= this.capacity) {
|
|
72
|
+
this.readPos = 0;
|
|
73
|
+
}
|
|
74
|
+
this.availableBytes -= size;
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get number of bytes available to read
|
|
79
|
+
*/
|
|
80
|
+
available() {
|
|
81
|
+
return this.availableBytes;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Reset the ring buffer
|
|
85
|
+
*/
|
|
86
|
+
reset() {
|
|
87
|
+
this.writePos = 0;
|
|
88
|
+
this.readPos = 0;
|
|
89
|
+
this.availableBytes = 0;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export class FFmpegDecoder {
|
|
93
|
+
constructor(videoPath, options = {}) {
|
|
94
|
+
this.metadata = null;
|
|
95
|
+
this.videoPath = videoPath;
|
|
96
|
+
this.options = {
|
|
97
|
+
pixelFormat: options.pixelFormat || 'gray',
|
|
98
|
+
maxBufferFrames: options.maxBufferFrames || 2,
|
|
99
|
+
skipFrames: options.skipFrames || 0
|
|
100
|
+
};
|
|
101
|
+
this.frameBuffer = new FrameBuffer(this.options.maxBufferFrames);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get video metadata
|
|
105
|
+
*/
|
|
106
|
+
async getMetadata() {
|
|
107
|
+
if (this.metadata) {
|
|
108
|
+
return this.metadata;
|
|
109
|
+
}
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
ffmpeg.ffprobe(this.videoPath, (err, metadata) => {
|
|
112
|
+
if (err) {
|
|
113
|
+
reject(new Error(`Failed to read video metadata: ${err.message}`));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const videoStream = metadata.streams.find(s => s.codec_type === 'video');
|
|
117
|
+
if (!videoStream) {
|
|
118
|
+
reject(new Error('No video stream found'));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const fps = this.parseFps(videoStream.r_frame_rate || videoStream.avg_frame_rate || '30/1');
|
|
122
|
+
const duration = parseFloat(String(metadata.format.duration || 0));
|
|
123
|
+
const totalFrames = Math.floor(duration * fps);
|
|
124
|
+
this.metadata = {
|
|
125
|
+
totalFrames,
|
|
126
|
+
duration,
|
|
127
|
+
fps,
|
|
128
|
+
resolution: {
|
|
129
|
+
width: videoStream.width || 0,
|
|
130
|
+
height: videoStream.height || 0
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
resolve(this.metadata);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Parse frame rate from FFmpeg format (e.g., "30000/1001")
|
|
139
|
+
*/
|
|
140
|
+
parseFps(fpsString) {
|
|
141
|
+
const parts = fpsString.split('/');
|
|
142
|
+
if (parts.length === 2) {
|
|
143
|
+
return parseInt(parts[0]) / parseInt(parts[1]);
|
|
144
|
+
}
|
|
145
|
+
return parseFloat(fpsString);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Extract frames as grayscale data
|
|
149
|
+
*
|
|
150
|
+
* @param onFrame Callback for each frame
|
|
151
|
+
* @param onProgress Optional progress callback
|
|
152
|
+
*/
|
|
153
|
+
async extractFrames(onFrame, onProgress) {
|
|
154
|
+
const metadata = await this.getMetadata();
|
|
155
|
+
const { width, height } = metadata.resolution;
|
|
156
|
+
return new Promise((resolve, reject) => {
|
|
157
|
+
let frameNumber = 0;
|
|
158
|
+
const ringBuffer = new RingBuffer(); // 8MB ring buffer
|
|
159
|
+
const frameSize = width * height; // Grayscale: 1 byte per pixel
|
|
160
|
+
const command = ffmpeg.default(this.videoPath)
|
|
161
|
+
.outputOptions([
|
|
162
|
+
'-f', 'image2pipe',
|
|
163
|
+
'-pix_fmt', 'gray',
|
|
164
|
+
'-vcodec', 'rawvideo'
|
|
165
|
+
])
|
|
166
|
+
.on('error', (err) => {
|
|
167
|
+
reject(new Error(`FFmpeg error: ${err.message}`));
|
|
168
|
+
})
|
|
169
|
+
.on('end', () => {
|
|
170
|
+
resolve();
|
|
171
|
+
});
|
|
172
|
+
const stream = command.pipe();
|
|
173
|
+
stream.on('data', async (chunk) => {
|
|
174
|
+
// Write chunk to ring buffer (no allocation, no copying)
|
|
175
|
+
ringBuffer.write(chunk);
|
|
176
|
+
// Process complete frames
|
|
177
|
+
while (ringBuffer.available() >= frameSize) {
|
|
178
|
+
const frameData = ringBuffer.read(frameSize);
|
|
179
|
+
// Skip frames if requested
|
|
180
|
+
if (this.options.skipFrames > 0 && frameNumber % (this.options.skipFrames + 1) !== 0) {
|
|
181
|
+
frameNumber++;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
// Create RawFrame
|
|
185
|
+
const frame = {
|
|
186
|
+
data: new Uint8Array(frameData),
|
|
187
|
+
width,
|
|
188
|
+
height,
|
|
189
|
+
stride: width,
|
|
190
|
+
pts: frameNumber / metadata.fps,
|
|
191
|
+
frameNumber
|
|
192
|
+
};
|
|
193
|
+
// Call callback
|
|
194
|
+
try {
|
|
195
|
+
await onFrame(frame);
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
stream.destroy();
|
|
199
|
+
reject(err);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// Progress callback
|
|
203
|
+
if (onProgress && frameNumber % 30 === 0) {
|
|
204
|
+
onProgress(frameNumber, metadata.totalFrames);
|
|
205
|
+
}
|
|
206
|
+
frameNumber++;
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
stream.on('error', (err) => {
|
|
210
|
+
reject(new Error(`Stream error: ${err.message}`));
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Extract a single frame at specific frame number
|
|
216
|
+
*/
|
|
217
|
+
async extractFrame(frameNumber) {
|
|
218
|
+
const metadata = await this.getMetadata();
|
|
219
|
+
const { width, height } = metadata.resolution;
|
|
220
|
+
const timestamp = frameNumber / metadata.fps;
|
|
221
|
+
return new Promise((resolve, reject) => {
|
|
222
|
+
const ringBuffer = new RingBuffer();
|
|
223
|
+
const frameSize = width * height;
|
|
224
|
+
const command = ffmpeg.default(this.videoPath)
|
|
225
|
+
.seekInput(timestamp)
|
|
226
|
+
.outputOptions([
|
|
227
|
+
'-vframes', '1',
|
|
228
|
+
'-f', 'image2pipe',
|
|
229
|
+
'-pix_fmt', 'gray',
|
|
230
|
+
'-vcodec', 'rawvideo'
|
|
231
|
+
])
|
|
232
|
+
.on('error', (err) => {
|
|
233
|
+
reject(new Error(`FFmpeg error: ${err.message}`));
|
|
234
|
+
});
|
|
235
|
+
const stream = command.pipe();
|
|
236
|
+
stream.on('data', (chunk) => {
|
|
237
|
+
ringBuffer.write(chunk);
|
|
238
|
+
if (ringBuffer.available() >= frameSize) {
|
|
239
|
+
const frameData = ringBuffer.read(frameSize);
|
|
240
|
+
const frame = {
|
|
241
|
+
data: new Uint8Array(frameData),
|
|
242
|
+
width,
|
|
243
|
+
height,
|
|
244
|
+
stride: width,
|
|
245
|
+
pts: timestamp,
|
|
246
|
+
frameNumber
|
|
247
|
+
};
|
|
248
|
+
resolve(frame);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
stream.on('error', (err) => {
|
|
252
|
+
reject(new Error(`Stream error: ${err.message}`));
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Get the frame buffer
|
|
258
|
+
*/
|
|
259
|
+
getFrameBuffer() {
|
|
260
|
+
return this.frameBuffer;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Clean up resources
|
|
264
|
+
*/
|
|
265
|
+
destroy() {
|
|
266
|
+
this.frameBuffer.clear();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
//# sourceMappingURL=ffmpeg-decoder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ffmpeg-decoder.js","sourceRoot":"","sources":["../../src/decoder/ffmpeg-decoder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frame Buffer - Circular buffer for frame management
|
|
3
|
+
*
|
|
4
|
+
* Maintains a circular buffer of frames for efficient scene detection
|
|
5
|
+
* Typically only needs to hold 2 frames (previous and current)
|
|
6
|
+
*/
|
|
7
|
+
import { RawFrame } from '../types';
|
|
8
|
+
import { BufferPool } from '../utils/buffer-pool';
|
|
9
|
+
export declare class FrameBuffer {
|
|
10
|
+
private bufferPool;
|
|
11
|
+
private frames;
|
|
12
|
+
private maxFrames;
|
|
13
|
+
private currentIndex;
|
|
14
|
+
/**
|
|
15
|
+
* Create a new frame buffer
|
|
16
|
+
*
|
|
17
|
+
* @param maxFrames Maximum number of frames to buffer (default: 2)
|
|
18
|
+
* @param bufferPool Optional buffer pool for memory reuse
|
|
19
|
+
*/
|
|
20
|
+
constructor(maxFrames?: number, bufferPool?: BufferPool);
|
|
21
|
+
/**
|
|
22
|
+
* Push a new frame into the buffer
|
|
23
|
+
*
|
|
24
|
+
* @param frame Frame to push
|
|
25
|
+
* @returns The frame that was evicted, if any
|
|
26
|
+
*/
|
|
27
|
+
push(frame: RawFrame): RawFrame | null;
|
|
28
|
+
/**
|
|
29
|
+
* Get frame at specific offset from current position
|
|
30
|
+
*
|
|
31
|
+
* @param offset Offset from current position (0 = most recent, 1 = previous, etc.)
|
|
32
|
+
* @returns Frame or null if not available
|
|
33
|
+
*/
|
|
34
|
+
get(offset?: number): RawFrame | null;
|
|
35
|
+
/**
|
|
36
|
+
* Get the most recent frame
|
|
37
|
+
*/
|
|
38
|
+
getCurrent(): RawFrame | null;
|
|
39
|
+
/**
|
|
40
|
+
* Get the previous frame
|
|
41
|
+
*/
|
|
42
|
+
getPrevious(): RawFrame | null;
|
|
43
|
+
/**
|
|
44
|
+
* Get both current and previous frames
|
|
45
|
+
*
|
|
46
|
+
* @returns [current, previous] or null if either is not available
|
|
47
|
+
*/
|
|
48
|
+
getCurrentAndPrevious(): [RawFrame, RawFrame] | null;
|
|
49
|
+
/**
|
|
50
|
+
* Allocate a buffer for a new frame
|
|
51
|
+
*
|
|
52
|
+
* @param size Buffer size in bytes
|
|
53
|
+
* @returns Uint8Array buffer
|
|
54
|
+
*/
|
|
55
|
+
allocateBuffer(size: number): Uint8Array;
|
|
56
|
+
/**
|
|
57
|
+
* Clear all frames from the buffer
|
|
58
|
+
*/
|
|
59
|
+
clear(): void;
|
|
60
|
+
/**
|
|
61
|
+
* Get the number of frames currently in the buffer
|
|
62
|
+
*/
|
|
63
|
+
size(): number;
|
|
64
|
+
/**
|
|
65
|
+
* Check if buffer is full
|
|
66
|
+
*/
|
|
67
|
+
isFull(): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Get buffer statistics
|
|
70
|
+
*/
|
|
71
|
+
getStats(): {
|
|
72
|
+
maxFrames: number;
|
|
73
|
+
currentFrames: number;
|
|
74
|
+
bufferPoolStats: {
|
|
75
|
+
size: number;
|
|
76
|
+
count: number;
|
|
77
|
+
}[];
|
|
78
|
+
totalPoolMemory: number;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=frame-buffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-buffer.d.ts","sourceRoot":"","sources":["../../src/decoder/frame-buffer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,qBAAa,WAAW;IACtB,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAa;IAEjC;;;;;OAKG;gBACS,SAAS,GAAE,MAAU,EAAE,UAAU,CAAC,EAAE,UAAU;IAM1D;;;;;OAKG;IACH,IAAI,CAAC,KAAK,EAAE,QAAQ,GAAG,QAAQ,GAAG,IAAI;IAiBtC;;;;;OAKG;IACH,GAAG,CAAC,MAAM,GAAE,MAAU,GAAG,QAAQ,GAAG,IAAI;IASxC;;OAEG;IACH,UAAU,IAAI,QAAQ,GAAG,IAAI;IAI7B;;OAEG;IACH,WAAW,IAAI,QAAQ,GAAG,IAAI;IAI9B;;;;OAIG;IACH,qBAAqB,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,IAAI;IAWpD;;;;;OAKG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU;IAIxC;;OAEG;IACH,KAAK,IAAI,IAAI;IAYb;;OAEG;IACH,IAAI,IAAI,MAAM;IAId;;OAEG;IACH,MAAM,IAAI,OAAO;IAIjB;;OAEG;IACH,QAAQ;;;;;;;;;CAQT"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frame Buffer - Circular buffer for frame management
|
|
3
|
+
*
|
|
4
|
+
* Maintains a circular buffer of frames for efficient scene detection
|
|
5
|
+
* Typically only needs to hold 2 frames (previous and current)
|
|
6
|
+
*/
|
|
7
|
+
import { BufferPool } from '../utils/buffer-pool';
|
|
8
|
+
export class FrameBuffer {
|
|
9
|
+
/**
|
|
10
|
+
* Create a new frame buffer
|
|
11
|
+
*
|
|
12
|
+
* @param maxFrames Maximum number of frames to buffer (default: 2)
|
|
13
|
+
* @param bufferPool Optional buffer pool for memory reuse
|
|
14
|
+
*/
|
|
15
|
+
constructor(maxFrames = 2, bufferPool) {
|
|
16
|
+
this.currentIndex = 0;
|
|
17
|
+
this.maxFrames = maxFrames;
|
|
18
|
+
this.bufferPool = bufferPool || new BufferPool();
|
|
19
|
+
this.frames = new Array(maxFrames).fill(null);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Push a new frame into the buffer
|
|
23
|
+
*
|
|
24
|
+
* @param frame Frame to push
|
|
25
|
+
* @returns The frame that was evicted, if any
|
|
26
|
+
*/
|
|
27
|
+
push(frame) {
|
|
28
|
+
const evictedFrame = this.frames[this.currentIndex];
|
|
29
|
+
// Release the evicted frame's buffer back to pool
|
|
30
|
+
if (evictedFrame) {
|
|
31
|
+
this.bufferPool.release(evictedFrame.data);
|
|
32
|
+
}
|
|
33
|
+
// Store the new frame
|
|
34
|
+
this.frames[this.currentIndex] = frame;
|
|
35
|
+
// Move to next position
|
|
36
|
+
this.currentIndex = (this.currentIndex + 1) % this.maxFrames;
|
|
37
|
+
return evictedFrame;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get frame at specific offset from current position
|
|
41
|
+
*
|
|
42
|
+
* @param offset Offset from current position (0 = most recent, 1 = previous, etc.)
|
|
43
|
+
* @returns Frame or null if not available
|
|
44
|
+
*/
|
|
45
|
+
get(offset = 0) {
|
|
46
|
+
if (offset < 0 || offset >= this.maxFrames) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const index = (this.currentIndex - 1 - offset + this.maxFrames) % this.maxFrames;
|
|
50
|
+
return this.frames[index];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get the most recent frame
|
|
54
|
+
*/
|
|
55
|
+
getCurrent() {
|
|
56
|
+
return this.get(0);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get the previous frame
|
|
60
|
+
*/
|
|
61
|
+
getPrevious() {
|
|
62
|
+
return this.get(1);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get both current and previous frames
|
|
66
|
+
*
|
|
67
|
+
* @returns [current, previous] or null if either is not available
|
|
68
|
+
*/
|
|
69
|
+
getCurrentAndPrevious() {
|
|
70
|
+
const current = this.getCurrent();
|
|
71
|
+
const previous = this.getPrevious();
|
|
72
|
+
if (!current || !previous) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return [current, previous];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Allocate a buffer for a new frame
|
|
79
|
+
*
|
|
80
|
+
* @param size Buffer size in bytes
|
|
81
|
+
* @returns Uint8Array buffer
|
|
82
|
+
*/
|
|
83
|
+
allocateBuffer(size) {
|
|
84
|
+
return this.bufferPool.acquire(size);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Clear all frames from the buffer
|
|
88
|
+
*/
|
|
89
|
+
clear() {
|
|
90
|
+
// Release all buffers back to pool
|
|
91
|
+
for (const frame of this.frames) {
|
|
92
|
+
if (frame) {
|
|
93
|
+
this.bufferPool.release(frame.data);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
this.frames = new Array(this.maxFrames).fill(null);
|
|
97
|
+
this.currentIndex = 0;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get the number of frames currently in the buffer
|
|
101
|
+
*/
|
|
102
|
+
size() {
|
|
103
|
+
return this.frames.filter(f => f !== null).length;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Check if buffer is full
|
|
107
|
+
*/
|
|
108
|
+
isFull() {
|
|
109
|
+
return this.size() === this.maxFrames;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get buffer statistics
|
|
113
|
+
*/
|
|
114
|
+
getStats() {
|
|
115
|
+
return {
|
|
116
|
+
maxFrames: this.maxFrames,
|
|
117
|
+
currentFrames: this.size(),
|
|
118
|
+
bufferPoolStats: this.bufferPool.getStats(),
|
|
119
|
+
totalPoolMemory: this.bufferPool.getTotalMemory()
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=frame-buffer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-buffer.js","sourceRoot":"","sources":["../../src/decoder/frame-buffer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scene Detector - Main orchestrator for scene change detection
|
|
3
|
+
*/
|
|
4
|
+
import { DetectionOptions, DetectionResult } from '../types';
|
|
5
|
+
export declare class SceneDetector {
|
|
6
|
+
private options;
|
|
7
|
+
private wasmBridge;
|
|
8
|
+
private state;
|
|
9
|
+
constructor(options?: DetectionOptions);
|
|
10
|
+
/**
|
|
11
|
+
* Detect scene changes in a video file
|
|
12
|
+
*/
|
|
13
|
+
detect(videoPath: string): Promise<DetectionResult>;
|
|
14
|
+
/**
|
|
15
|
+
* Destroy the detector and clean up resources
|
|
16
|
+
*/
|
|
17
|
+
destroy(): void;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../../src/detection/detector.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EACL,gBAAgB,EAChB,eAAe,EAKhB,MAAM,UAAU,CAAC;AAOlB,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,KAAK,CAAiB;gBAElB,OAAO,GAAE,gBAAqB;IA0B1C;;OAEG;IACG,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAsHzD;;OAEG;IACH,OAAO,IAAI,IAAI;CAKhB"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scene Detector - Main orchestrator for scene change detection
|
|
3
|
+
*/
|
|
4
|
+
import { FFmpegDecoder } from '../decoder/ffmpeg-decoder';
|
|
5
|
+
import { WasmBridge } from './wasm-bridge';
|
|
6
|
+
import { formatTimecode, calculateFcode, validateFrame } from '../utils/frame-processor';
|
|
7
|
+
export class SceneDetector {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
// Set default options
|
|
10
|
+
this.options = {
|
|
11
|
+
sensitivity: options.sensitivity || 'medium',
|
|
12
|
+
customThresholds: options.customThresholds || { intraThresh: 2000, intraThresh2: 90 },
|
|
13
|
+
searchRange: options.searchRange || 'medium',
|
|
14
|
+
workers: options.workers || 1, // Multi-threading not implemented yet
|
|
15
|
+
progressive: options.progressive || { enabled: false, initialStep: 1, refinementSteps: [] },
|
|
16
|
+
temporalSmoothing: options.temporalSmoothing || { enabled: false, windowSize: 5, minConsecutive: 2 },
|
|
17
|
+
frameExtraction: options.frameExtraction || { pixelFormat: 'gray', maxBufferFrames: 2 },
|
|
18
|
+
onProgress: options.onProgress || (() => { }),
|
|
19
|
+
onScene: options.onScene || (() => { }),
|
|
20
|
+
format: options.format || 'json'
|
|
21
|
+
};
|
|
22
|
+
this.wasmBridge = new WasmBridge();
|
|
23
|
+
// Initialize detection state
|
|
24
|
+
this.state = {
|
|
25
|
+
intraCount: 1,
|
|
26
|
+
fcode: 4,
|
|
27
|
+
prevFrame: null,
|
|
28
|
+
curFrame: null
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Detect scene changes in a video file
|
|
33
|
+
*/
|
|
34
|
+
async detect(videoPath) {
|
|
35
|
+
// Initialize WASM module
|
|
36
|
+
await this.wasmBridge.init();
|
|
37
|
+
// Create decoder
|
|
38
|
+
const decoder = new FFmpegDecoder(videoPath, {
|
|
39
|
+
pixelFormat: this.options.frameExtraction.pixelFormat,
|
|
40
|
+
maxBufferFrames: this.options.frameExtraction.maxBufferFrames,
|
|
41
|
+
skipFrames: this.options.frameExtraction.skipFrames
|
|
42
|
+
});
|
|
43
|
+
// Get video metadata
|
|
44
|
+
const metadata = await decoder.getMetadata();
|
|
45
|
+
// Calculate fcode from search range
|
|
46
|
+
this.state.fcode = calculateFcode(this.options.searchRange, metadata.resolution.width, metadata.resolution.height);
|
|
47
|
+
// Pre-allocate WASM buffers (eliminates per-frame allocation overhead)
|
|
48
|
+
this.wasmBridge.allocateBuffers(metadata.resolution.width, metadata.resolution.height);
|
|
49
|
+
// Initialize scene list (frame 0 is always a scene change)
|
|
50
|
+
const scenes = [
|
|
51
|
+
{
|
|
52
|
+
frameNumber: 0,
|
|
53
|
+
timestamp: 0,
|
|
54
|
+
timecode: '00:00:00.000'
|
|
55
|
+
}
|
|
56
|
+
];
|
|
57
|
+
// Processing statistics
|
|
58
|
+
const startTime = Date.now();
|
|
59
|
+
let processedFrames = 0;
|
|
60
|
+
// Process frames
|
|
61
|
+
await decoder.extractFrames(async (frame) => {
|
|
62
|
+
validateFrame(frame);
|
|
63
|
+
// Update current frame
|
|
64
|
+
this.state.curFrame = frame;
|
|
65
|
+
// Need at least 2 frames to detect scene change
|
|
66
|
+
if (this.state.prevFrame) {
|
|
67
|
+
const isSceneChange = this.wasmBridge.detectSceneChange(this.state.prevFrame, this.state.curFrame, this.state.intraCount, this.state.fcode);
|
|
68
|
+
if (isSceneChange) {
|
|
69
|
+
const scene = {
|
|
70
|
+
frameNumber: frame.frameNumber,
|
|
71
|
+
timestamp: frame.pts,
|
|
72
|
+
timecode: formatTimecode(frame.pts)
|
|
73
|
+
};
|
|
74
|
+
scenes.push(scene);
|
|
75
|
+
// Call scene callback
|
|
76
|
+
this.options.onScene(scene);
|
|
77
|
+
// Reset intraCount
|
|
78
|
+
this.state.intraCount = 1;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
// Increment intraCount
|
|
82
|
+
this.state.intraCount++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Move current frame to previous
|
|
86
|
+
this.state.prevFrame = this.state.curFrame;
|
|
87
|
+
processedFrames++;
|
|
88
|
+
}, (current, total) => {
|
|
89
|
+
// Progress callback
|
|
90
|
+
const progress = {
|
|
91
|
+
currentFrame: current,
|
|
92
|
+
totalFrames: total,
|
|
93
|
+
percent: Math.round((current / total) * 100)
|
|
94
|
+
};
|
|
95
|
+
// Calculate ETA
|
|
96
|
+
const elapsed = (Date.now() - startTime) / 1000;
|
|
97
|
+
const fps = current / elapsed;
|
|
98
|
+
const remaining = (total - current) / fps;
|
|
99
|
+
progress.eta = remaining;
|
|
100
|
+
this.options.onProgress(progress);
|
|
101
|
+
});
|
|
102
|
+
// Calculate statistics
|
|
103
|
+
const endTime = Date.now();
|
|
104
|
+
const processingTime = (endTime - startTime) / 1000;
|
|
105
|
+
const framesPerSecond = processedFrames / processingTime;
|
|
106
|
+
// Clean up
|
|
107
|
+
decoder.destroy();
|
|
108
|
+
return {
|
|
109
|
+
scenes,
|
|
110
|
+
metadata,
|
|
111
|
+
stats: {
|
|
112
|
+
processingTime,
|
|
113
|
+
framesPerSecond
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Destroy the detector and clean up resources
|
|
119
|
+
*/
|
|
120
|
+
destroy() {
|
|
121
|
+
this.wasmBridge.destroy();
|
|
122
|
+
this.state.prevFrame = null;
|
|
123
|
+
this.state.curFrame = null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detector.js","sourceRoot":"","sources":["../../src/detection/detector.ts"],"names":[],"mappings":"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"}
|