@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.
Files changed (41) hide show
  1. package/README.md +280 -0
  2. package/bin/cli.js +293 -0
  3. package/dist/decoder/ffmpeg-decoder.d.ts +50 -0
  4. package/dist/decoder/ffmpeg-decoder.d.ts.map +1 -0
  5. package/dist/decoder/ffmpeg-decoder.js +269 -0
  6. package/dist/decoder/ffmpeg-decoder.js.map +1 -0
  7. package/dist/decoder/frame-buffer.d.ts +81 -0
  8. package/dist/decoder/frame-buffer.d.ts.map +1 -0
  9. package/dist/decoder/frame-buffer.js +123 -0
  10. package/dist/decoder/frame-buffer.js.map +1 -0
  11. package/dist/detection/detector.d.ts +19 -0
  12. package/dist/detection/detector.d.ts.map +1 -0
  13. package/dist/detection/detector.js +126 -0
  14. package/dist/detection/detector.js.map +1 -0
  15. package/dist/detection/wasm-bridge.d.ts +82 -0
  16. package/dist/detection/wasm-bridge.d.ts.map +1 -0
  17. package/dist/detection/wasm-bridge.js +182 -0
  18. package/dist/detection/wasm-bridge.js.map +1 -0
  19. package/dist/detection.wasm.js +2 -0
  20. package/dist/detection.wasm.wasm +0 -0
  21. package/dist/index.d.ts +54 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +63 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/keyframes.cjs.js +985 -0
  26. package/dist/keyframes.cjs.js.map +1 -0
  27. package/dist/keyframes.esm.js +946 -0
  28. package/dist/keyframes.esm.js.map +1 -0
  29. package/dist/types/index.d.ts +225 -0
  30. package/dist/types/index.d.ts.map +1 -0
  31. package/dist/types/index.js +5 -0
  32. package/dist/types/index.js.map +1 -0
  33. package/dist/utils/buffer-pool.d.ts +44 -0
  34. package/dist/utils/buffer-pool.d.ts.map +1 -0
  35. package/dist/utils/buffer-pool.js +81 -0
  36. package/dist/utils/buffer-pool.js.map +1 -0
  37. package/dist/utils/frame-processor.d.ts +48 -0
  38. package/dist/utils/frame-processor.d.ts.map +1 -0
  39. package/dist/utils/frame-processor.js +112 -0
  40. package/dist/utils/frame-processor.js.map +1 -0
  41. package/package.json +77 -0
package/README.md ADDED
@@ -0,0 +1,280 @@
1
+ # scenecut
2
+
3
+ Fast, accurate scene change detection for Node.js using Xvid's motion estimation algorithm compiled to WebAssembly.
4
+
5
+ ## Features
6
+
7
+ - **Fast**: WebAssembly-accelerated motion estimation (35-45 fps on typical hardware)
8
+ - **Accurate**: Uses Xvid's proven motion estimation algorithm from vapoursynth-wwxd
9
+ - **Multiple output formats**: Aegisub keyframes, timecodes, CSV, JSON
10
+ - **Easy to use**: Simple CLI and programmatic API
11
+ - **Cross-platform**: Works on Windows, Linux, and macOS
12
+
13
+ ## Installation
14
+
15
+ ### Global Installation (CLI)
16
+
17
+ ```bash
18
+ npm install -g @doedja/scenecut
19
+ ```
20
+
21
+ ### Local Installation (API)
22
+
23
+ ```bash
24
+ npm install @doedja/scenecut
25
+ ```
26
+
27
+ ## CLI Usage
28
+
29
+ ### Basic Usage
30
+
31
+ ```bash
32
+ # Simple - detects scenes and saves to Aegisub format (default)
33
+ # Creates input_keyframes.txt
34
+ scenecut "input.mkv"
35
+
36
+ # Specify custom output filename
37
+ scenecut "video.mkv" -o keyframes.txt
38
+
39
+ # For timecode output
40
+ scenecut "video.mp4" --format timecode -o timecodes.txt
41
+
42
+ # JSON format with full metadata
43
+ scenecut "movie.avi" --format json
44
+
45
+ # CSV format for spreadsheet analysis
46
+ scenecut "movie.avi" --format csv -o scenes.csv
47
+ ```
48
+
49
+ ### CLI Options
50
+
51
+ | Option | Alias | Description | Default |
52
+ |--------|-------|-------------|---------|
53
+ | `--format` | `-f` | Output format: `aegisub`, `json`, `csv`, `timecode` | `aegisub` |
54
+ | `--output` | `-o` | Output file path | `{filename}_keyframes.txt` |
55
+ | `--sensitivity` | `-s` | Detection sensitivity: `low`, `medium`, `high` | `medium` |
56
+ | `--quiet` | `-q` | Suppress progress output | `false` |
57
+ | `--verbose` | `-v` | Show detailed output including each scene | `false` |
58
+ | `--help` | `-h` | Show help message | - |
59
+
60
+ ### Examples
61
+
62
+ ```bash
63
+ # Default - creates anime_keyframes.txt in Aegisub format
64
+ scenecut "anime.mkv"
65
+
66
+ # High sensitivity for subtle scene changes
67
+ scenecut "anime.mkv" --sensitivity high
68
+
69
+ # JSON format with full metadata
70
+ scenecut "video.mp4" --format json -o results.json
71
+
72
+ # Verbose mode with detailed scene information
73
+ scenecut "movie.mkv" --verbose
74
+ ```
75
+
76
+ ## Output Formats
77
+
78
+ ### Aegisub Format (`.txt`)
79
+
80
+ Aegisub keyframes format for subtitle timing:
81
+
82
+ ```
83
+ # keyframe format v1
84
+ fps 23.976
85
+ 0
86
+ 143
87
+ 287
88
+ ```
89
+
90
+ **Aegisub Workflow:**
91
+ 1. Generate keyframes: `scenecut "video.mkv" -f aegisub -o keyframes.txt`
92
+ 2. In Aegisub: **Video** → **Open Keyframes** → Select `keyframes.txt`
93
+ 3. Keyframes appear as visual markers on the timeline for precise subtitle timing
94
+
95
+ ### Timecode Format (`.txt`)
96
+
97
+ Simple timecode list (HH:MM:SS.mmm):
98
+
99
+ ```
100
+ 00:00:00.000
101
+ 00:00:05.964
102
+ 00:00:11.970
103
+ ```
104
+
105
+ ### CSV Format (`.csv`)
106
+
107
+ Spreadsheet-compatible format:
108
+
109
+ ```csv
110
+ frame,timestamp,timecode
111
+ 0,0.0,00:00:00.000
112
+ 143,5.964,00:00:05.964
113
+ 287,11.970,00:00:11.970
114
+ ```
115
+
116
+ ### JSON Format (`.json`)
117
+
118
+ Complete metadata and scene information:
119
+
120
+ ```json
121
+ {
122
+ "scenes": [
123
+ {
124
+ "frameNumber": 0,
125
+ "timestamp": 0.0,
126
+ "timecode": "00:00:00.000"
127
+ },
128
+ {
129
+ "frameNumber": 143,
130
+ "timestamp": 5.964,
131
+ "timecode": "00:00:05.964"
132
+ }
133
+ ],
134
+ "metadata": {
135
+ "totalFrames": 3000,
136
+ "duration": 125.08,
137
+ "fps": 23.976,
138
+ "resolution": {
139
+ "width": 1920,
140
+ "height": 1080
141
+ }
142
+ }
143
+ }
144
+ ```
145
+
146
+ ## Programmatic API
147
+
148
+ ### Basic Usage
149
+
150
+ ```javascript
151
+ const { detectSceneChanges } = require('@doedja/scenecut');
152
+
153
+ (async () => {
154
+ const results = await detectSceneChanges('input.mp4');
155
+
156
+ console.log(`Found ${results.scenes.length} scenes`);
157
+ results.scenes.forEach(scene => {
158
+ console.log(`Scene at frame ${scene.frameNumber} (${scene.timecode})`);
159
+ });
160
+ })();
161
+ ```
162
+
163
+ ### Advanced Usage with Options
164
+
165
+ ```javascript
166
+ const { detectSceneChanges } = require('@doedja/scenecut');
167
+
168
+ const results = await detectSceneChanges('input.mp4', {
169
+ sensitivity: 'high', // 'low' | 'medium' | 'high'
170
+ searchRange: 'medium', // Motion search range
171
+
172
+ // Progress callback
173
+ onProgress: (progress) => {
174
+ console.log(`Progress: ${progress.percent}%`);
175
+ console.log(`Frame: ${progress.currentFrame}/${progress.totalFrames}`);
176
+ console.log(`FPS: ${progress.fps}, ETA: ${progress.eta}s`);
177
+ },
178
+
179
+ // Scene detection callback
180
+ onScene: (scene) => {
181
+ console.log(`Scene detected at frame ${scene.frameNumber}`);
182
+ console.log(`Timecode: ${scene.timecode}`);
183
+ }
184
+ });
185
+
186
+ console.log('Detection complete!');
187
+ console.log(`Total scenes: ${results.scenes.length}`);
188
+ console.log(`Video duration: ${results.metadata.duration}s`);
189
+ console.log(`Resolution: ${results.metadata.resolution.width}x${results.metadata.resolution.height}`);
190
+ ```
191
+
192
+ ### API Reference
193
+
194
+ #### `detectSceneChanges(videoPath, options?)`
195
+
196
+ Detects scene changes in a video file.
197
+
198
+ **Parameters:**
199
+ - `videoPath` (string): Path to input video file
200
+ - `options` (object, optional):
201
+ - `sensitivity` ('low' | 'medium' | 'high'): Detection sensitivity (default: 'medium')
202
+ - `searchRange` ('auto' | 'small' | 'medium' | 'large'): Motion search range (default: 'medium')
203
+ - `onProgress` (function): Callback for progress updates
204
+ - `onScene` (function): Callback for each detected scene
205
+
206
+ **Returns:** Promise<DetectionResult>
207
+
208
+ **DetectionResult:**
209
+ ```typescript
210
+ {
211
+ scenes: Array<{
212
+ frameNumber: number;
213
+ timestamp: number; // Seconds
214
+ timecode: string; // HH:MM:SS.mmm
215
+ }>;
216
+ metadata: {
217
+ totalFrames: number;
218
+ duration: number; // Seconds
219
+ fps: number;
220
+ resolution: {
221
+ width: number;
222
+ height: number;
223
+ };
224
+ };
225
+ }
226
+ ```
227
+
228
+ ## Supported Video Formats
229
+
230
+ Keyframes supports any video format that FFmpeg can decode, including:
231
+
232
+ - MP4 (`.mp4`, `.m4v`)
233
+ - Matroska (`.mkv`)
234
+ - AVI (`.avi`)
235
+ - WebM (`.webm`)
236
+ - MOV (`.mov`)
237
+ - FLV (`.flv`)
238
+ - And many more...
239
+
240
+ ## How It Works
241
+
242
+ Keyframes uses Xvid's motion estimation algorithm to detect scene changes:
243
+
244
+ 1. **Frame Extraction**: FFmpeg extracts grayscale frames from the video
245
+ 2. **Motion Analysis**: WebAssembly-compiled C code analyzes motion vectors between consecutive frames
246
+ 3. **Scene Detection**: Frames with high motion complexity are identified as scene changes
247
+ 4. **Output Formatting**: Results are formatted according to the requested output format
248
+
249
+ The algorithm is based on [vapoursynth-wwxd](https://github.com/dubhater/vapoursynth-wwxd) by dubhater, which itself uses Xvid's motion estimation code.
250
+
251
+ ## Performance
252
+
253
+ Optimized for speed and accuracy:
254
+ - **Processing speed**: 35-45 fps on 1080p video (modern hardware)
255
+ - **Memory usage**: ~200-300 MB with efficient buffer management
256
+ - **Accuracy**: Matches vapoursynth-wwxd output (100% accurate)
257
+ - **Optimizations**: WASM SIMD, pre-allocated buffers, ring buffer streaming
258
+
259
+ ## Requirements
260
+
261
+ - **Node.js**: 18.0.0 or higher
262
+ - **FFmpeg**: Automatically installed via `@ffmpeg-installer/ffmpeg`
263
+
264
+ ## License
265
+
266
+ GPL-2.0
267
+
268
+ This project is based on:
269
+ - [vapoursynth-wwxd](https://github.com/dubhater/vapoursynth-wwxd) by dubhater (GPL-2.0)
270
+ - Xvid's motion estimation algorithm (GPL-2.0)
271
+
272
+ ## Contributing
273
+
274
+ Contributions are welcome! Please feel free to submit issues or pull requests.
275
+
276
+ ## Credits
277
+
278
+ - Original vapoursynth-wwxd plugin: [dubhater](https://github.com/dubhater)
279
+ - Xvid motion estimation algorithm: [Xvid Team](https://www.xvid.com)
280
+ - JavaScript/WASM port: This project
package/bin/cli.js ADDED
@@ -0,0 +1,293 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Keyframes CLI - Simple command-line interface for scene detection
5
+ *
6
+ * Usage:
7
+ * keyframes input.mp4
8
+ * keyframes input.mkv --output results.json
9
+ * keyframes video.mp4 --sensitivity high
10
+ */
11
+
12
+ const { detectSceneChanges } = require('../dist/keyframes.cjs.js');
13
+ const path = require('path');
14
+ const fs = require('fs');
15
+
16
+ // Parse command line arguments
17
+ const args = process.argv.slice(2);
18
+
19
+ // Help text
20
+ const HELP = `
21
+ Scenecut - Scene change detection for videos
22
+
23
+ Usage:
24
+ scenecut <video-file> [options]
25
+
26
+ Examples:
27
+ scenecut input.mp4
28
+ scenecut video.mkv --output keyframes.txt --format aegisub
29
+ scenecut movie.mp4 --sensitivity high --format timecode
30
+ scenecut video.mp4 --format csv --output scenes.csv
31
+
32
+ Options:
33
+ --output, -o <file> Output file (default: {filename}_keyframes.txt)
34
+ --format, -f <format> Output format: json|csv|aegisub|timecode (default: aegisub)
35
+ --sensitivity, -s <level> Sensitivity: low|medium|high (default: medium)
36
+ --quiet, -q Suppress progress output
37
+ --verbose, -v Show detailed output
38
+ --help, -h Show this help
39
+
40
+ Formats:
41
+ json JSON with full metadata
42
+ csv CSV with frame,timestamp,timecode
43
+ aegisub (or txt) Aegisub keyframes format (frame numbers)
44
+ timecode (or tc) Simple timecode list (HH:MM:SS.mmm)
45
+
46
+ Video Formats:
47
+ Supports MP4, MKV, AVI, WebM, MOV, and any format FFmpeg supports
48
+
49
+ Output:
50
+ Results are saved to the output file and printed to stdout
51
+ `;
52
+
53
+ // Show help
54
+ if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
55
+ console.log(HELP);
56
+ process.exit(0);
57
+ }
58
+
59
+ // Parse arguments
60
+ let videoPath = null;
61
+ let outputPath = null; // Will be derived from video filename if not specified
62
+ let outputFormat = 'aegisub'; // Default to Aegisub format
63
+ let sensitivity = 'medium';
64
+ let quiet = false;
65
+ let verbose = false;
66
+
67
+ for (let i = 0; i < args.length; i++) {
68
+ const arg = args[i];
69
+
70
+ if (arg === '--output' || arg === '-o') {
71
+ outputPath = args[++i];
72
+ } else if (arg === '--format' || arg === '-f') {
73
+ outputFormat = args[++i];
74
+ } else if (arg === '--sensitivity' || arg === '-s') {
75
+ sensitivity = args[++i];
76
+ } else if (arg === '--quiet' || arg === '-q') {
77
+ quiet = true;
78
+ } else if (arg === '--verbose' || arg === '-v') {
79
+ verbose = true;
80
+ } else if (!arg.startsWith('-')) {
81
+ videoPath = arg;
82
+ } else {
83
+ console.error(`Unknown option: ${arg}`);
84
+ console.error('Run "scenecut --help" for usage');
85
+ process.exit(1);
86
+ }
87
+ }
88
+
89
+ // Validate video path
90
+ if (!videoPath) {
91
+ console.error('Error: No video file specified');
92
+ console.error('Usage: scenecut <video-file>');
93
+ console.error('Run "scenecut --help" for more information');
94
+ process.exit(1);
95
+ }
96
+
97
+ // Resolve video path
98
+ videoPath = path.resolve(videoPath);
99
+
100
+ if (!fs.existsSync(videoPath)) {
101
+ console.error(`Error: Video file not found: ${videoPath}`);
102
+ process.exit(1);
103
+ }
104
+
105
+ // Generate default output path if not specified
106
+ if (!outputPath) {
107
+ const videoBasename = path.basename(videoPath, path.extname(videoPath));
108
+ const extension = (outputFormat === 'json') ? '.json' :
109
+ (outputFormat === 'csv') ? '.csv' : '.txt';
110
+ outputPath = `${videoBasename}_keyframes${extension}`;
111
+ }
112
+
113
+ // Get file info
114
+ const stats = fs.statSync(videoPath);
115
+ const fileSize = (stats.size / (1024 * 1024)).toFixed(2);
116
+
117
+ // Main processing function
118
+ async function run() {
119
+ if (!quiet) {
120
+ console.log('Scenecut - Scene Detection');
121
+ console.log('='.repeat(60));
122
+ console.log(`Input: ${videoPath}`);
123
+ console.log(`Size: ${fileSize} MB`);
124
+ console.log(`Output: ${path.resolve(outputPath)}`);
125
+ console.log('='.repeat(60));
126
+ console.log();
127
+ }
128
+
129
+ const startTime = Date.now();
130
+ let lastProgressTime = startTime;
131
+ let lastProgressFrame = 0;
132
+ let sceneCount = 0;
133
+
134
+ try {
135
+ const results = await detectSceneChanges(videoPath, {
136
+ sensitivity,
137
+ searchRange: 'medium',
138
+ onProgress: (progress) => {
139
+ if (quiet) return;
140
+
141
+ const now = Date.now();
142
+ // Update every 3 seconds
143
+ if (now - lastProgressTime > 3000 || progress.percent === 100) {
144
+ const framesSinceLastUpdate = progress.currentFrame - lastProgressFrame;
145
+ const timeSinceLastUpdate = (now - lastProgressTime) / 1000;
146
+ const currentFps = framesSinceLastUpdate / timeSinceLastUpdate;
147
+
148
+ const progressBar = createProgressBar(progress.percent);
149
+ const etaStr = progress.eta ? ` ETA: ${formatTime(progress.eta)}` : '';
150
+
151
+ process.stdout.write(
152
+ `\r${progressBar} ${progress.percent.toString().padStart(3)}% ` +
153
+ `[${currentFps.toFixed(1)} fps]${etaStr}${' '.repeat(10)}`
154
+ );
155
+
156
+ lastProgressTime = now;
157
+ lastProgressFrame = progress.currentFrame;
158
+ }
159
+ },
160
+ onScene: (scene) => {
161
+ sceneCount++;
162
+ if (verbose && !quiet) {
163
+ console.log();
164
+ console.log(` Scene ${sceneCount}: Frame ${scene.frameNumber} at ${scene.timecode}`);
165
+ }
166
+ }
167
+ });
168
+
169
+ const endTime = Date.now();
170
+ const elapsed = (endTime - startTime) / 1000;
171
+
172
+ if (!quiet) {
173
+ console.log('\n');
174
+ console.log('='.repeat(60));
175
+ console.log('Complete!');
176
+ console.log('='.repeat(60));
177
+ console.log(`Scenes detected: ${results.scenes.length}`);
178
+ console.log(`Processing time: ${formatTime(elapsed)}`);
179
+ console.log(`Processing speed: ${(results.metadata.totalFrames / elapsed).toFixed(1)} fps`);
180
+ console.log('='.repeat(60));
181
+ }
182
+
183
+ // Format output
184
+ let output;
185
+ if (outputFormat === 'csv') {
186
+ output = formatCSV(results);
187
+ if (outputPath.endsWith('.json')) {
188
+ outputPath = outputPath.replace('.json', '.csv');
189
+ }
190
+ } else if (outputFormat === 'aegisub' || outputFormat === 'txt') {
191
+ output = formatAegisub(results);
192
+ if (outputPath.endsWith('.json')) {
193
+ outputPath = outputPath.replace('.json', '.txt');
194
+ }
195
+ } else if (outputFormat === 'timecode' || outputFormat === 'tc') {
196
+ output = formatTimecode(results);
197
+ if (outputPath.endsWith('.json')) {
198
+ outputPath = outputPath.replace('.json', '.txt');
199
+ }
200
+ } else {
201
+ output = JSON.stringify(results, null, 2);
202
+ }
203
+
204
+ // Save to file
205
+ fs.writeFileSync(outputPath, output);
206
+
207
+ if (!quiet) {
208
+ console.log(`Results saved to: ${path.resolve(outputPath)}`);
209
+ console.log();
210
+
211
+ // Print scene list
212
+ console.log('Scene List:');
213
+ results.scenes.forEach((scene, i) => {
214
+ console.log(` ${(i + 1).toString().padStart(3)}. Frame ${scene.frameNumber.toString().padStart(6)} at ${scene.timecode}`);
215
+ });
216
+ } else {
217
+ // In quiet mode, just print the output
218
+ console.log(output);
219
+ }
220
+
221
+ process.exit(0);
222
+
223
+ } catch (error) {
224
+ console.error();
225
+ console.error('Error:', error.message);
226
+ if (verbose) {
227
+ console.error(error.stack);
228
+ }
229
+ process.exit(1);
230
+ }
231
+ }
232
+
233
+ // Helper functions
234
+ function createProgressBar(percent) {
235
+ const width = 30;
236
+ const filled = Math.round((percent / 100) * width);
237
+ const empty = width - filled;
238
+ return '[' + '█'.repeat(filled) + '░'.repeat(empty) + ']';
239
+ }
240
+
241
+ function formatTime(seconds) {
242
+ const h = Math.floor(seconds / 3600);
243
+ const m = Math.floor((seconds % 3600) / 60);
244
+ const s = Math.floor(seconds % 60);
245
+
246
+ if (h > 0) {
247
+ return `${h}h ${m}m ${s}s`;
248
+ } else if (m > 0) {
249
+ return `${m}m ${s}s`;
250
+ } else {
251
+ return `${s}s`;
252
+ }
253
+ }
254
+
255
+ function formatCSV(results) {
256
+ let csv = 'frame,timestamp,timecode\n';
257
+ results.scenes.forEach(scene => {
258
+ csv += `${scene.frameNumber},${scene.timestamp},${scene.timecode || ''}\n`;
259
+ });
260
+ return csv;
261
+ }
262
+
263
+ function formatAegisub(results) {
264
+ // Aegisub keyframes format - simple text file with frame numbers
265
+ // One frame number per line
266
+ let output = '# keyframe format v1\n';
267
+ output += `fps ${results.metadata.fps}\n`;
268
+ results.scenes.forEach(scene => {
269
+ output += `${scene.frameNumber}\n`;
270
+ });
271
+ return output;
272
+ }
273
+
274
+ function formatTimecode(results) {
275
+ // Simple timecode list - one per line
276
+ // Can be used with various subtitle tools
277
+ let output = '';
278
+ results.scenes.forEach(scene => {
279
+ output += `${scene.timecode || formatTimecodeFull(scene.timestamp)}\n`;
280
+ });
281
+ return output;
282
+ }
283
+
284
+ function formatTimecodeFull(seconds) {
285
+ const h = Math.floor(seconds / 3600);
286
+ const m = Math.floor((seconds % 3600) / 60);
287
+ const s = Math.floor(seconds % 60);
288
+ const ms = Math.floor((seconds % 1) * 1000);
289
+ return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}.${ms.toString().padStart(3, '0')}`;
290
+ }
291
+
292
+ // Run
293
+ run();
@@ -0,0 +1,50 @@
1
+ /**
2
+ * FFmpeg Decoder - Extract frames from video files
3
+ *
4
+ * Uses fluent-ffmpeg to extract grayscale frames for scene detection
5
+ */
6
+ import { RawFrame, VideoMetadata } from '../types';
7
+ import { FrameBuffer } from './frame-buffer';
8
+ export interface DecoderOptions {
9
+ /** Pixel format for extraction (default: 'gray') */
10
+ pixelFormat?: 'gray' | 'yuv420p';
11
+ /** Maximum frames to buffer in memory */
12
+ maxBufferFrames?: number;
13
+ /** Skip every N frames for testing */
14
+ skipFrames?: number;
15
+ }
16
+ export declare class FFmpegDecoder {
17
+ private videoPath;
18
+ private options;
19
+ private metadata;
20
+ private frameBuffer;
21
+ constructor(videoPath: string, options?: DecoderOptions);
22
+ /**
23
+ * Get video metadata
24
+ */
25
+ getMetadata(): Promise<VideoMetadata>;
26
+ /**
27
+ * Parse frame rate from FFmpeg format (e.g., "30000/1001")
28
+ */
29
+ private parseFps;
30
+ /**
31
+ * Extract frames as grayscale data
32
+ *
33
+ * @param onFrame Callback for each frame
34
+ * @param onProgress Optional progress callback
35
+ */
36
+ extractFrames(onFrame: (frame: RawFrame) => Promise<void> | void, onProgress?: (current: number, total: number) => void): Promise<void>;
37
+ /**
38
+ * Extract a single frame at specific frame number
39
+ */
40
+ extractFrame(frameNumber: number): Promise<RawFrame>;
41
+ /**
42
+ * Get the frame buffer
43
+ */
44
+ getFrameBuffer(): FrameBuffer;
45
+ /**
46
+ * Clean up resources
47
+ */
48
+ destroy(): void;
49
+ }
50
+ //# sourceMappingURL=ffmpeg-decoder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ffmpeg-decoder.d.ts","sourceRoot":"","sources":["../../src/decoder/ffmpeg-decoder.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAsG7C,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,yCAAyC;IACzC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sCAAsC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,WAAW,CAAc;gBAErB,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB;IAU3D;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,aAAa,CAAC;IAqC3C;;OAEG;IACH,OAAO,CAAC,QAAQ;IAQhB;;;;;OAKG;IACG,aAAa,CACjB,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,EAClD,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GACpD,OAAO,CAAC,IAAI,CAAC;IAwEhB;;OAEG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAgD1D;;OAEG;IACH,cAAc,IAAI,WAAW;IAI7B;;OAEG;IACH,OAAO,IAAI,IAAI;CAGhB"}