scrypted-detection-trainer 0.1.8 → 0.1.9

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/out/plugin.zip CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scrypted-detection-trainer",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Collect and label detections to fine-tune the Scrypted NVR object detection model.",
5
5
  "keywords": [
6
6
  "scrypted-plugin"
package/src/main.ts CHANGED
@@ -10,6 +10,8 @@ import sdk, {
10
10
  ObjectsDetected,
11
11
  ObjectDetector,
12
12
  } from '@scrypted/sdk';
13
+ import fs from 'fs';
14
+ import path from 'path';
13
15
 
14
16
  const { systemManager, deviceManager, mediaManager } = sdk;
15
17
 
@@ -55,13 +57,16 @@ class DetectionTrainer extends ScryptedDeviceBase implements Settings, HttpReque
55
57
  private lastCapture = new Map<string, number>();
56
58
  // Map<captureId, CaptureRecord>
57
59
  private captures = new Map<string, CaptureRecord>();
58
- // Map<captureId, jpegBuffer>
59
- private images = new Map<string, Buffer>();
60
60
  // Active event listeners
61
61
  private listeners: (() => void)[] = [];
62
+ // Directory for storing images on disk
63
+ private imgDir: string;
62
64
 
63
65
  constructor(nativeId?: string) {
64
66
  super(nativeId);
67
+ // Use a stable directory inside the plugin's volume
68
+ this.imgDir = path.join(process.env.SCRYPTED_PLUGIN_VOLUME || '/tmp', 'detection-trainer-images');
69
+ try { fs.mkdirSync(this.imgDir, { recursive: true }); } catch {}
65
70
  this.loadState();
66
71
  this.registerListeners();
67
72
  }
@@ -79,26 +84,43 @@ class DetectionTrainer extends ScryptedDeviceBase implements Settings, HttpReque
79
84
  } catch (e) {
80
85
  this.console.warn('Could not load captures from storage:', e);
81
86
  }
82
- // images are stored as individual items
87
+ // Clean up any old base64 image entries from previous versions
83
88
  for (const [id] of this.captures) {
84
- const raw = this.storage.getItem(`img:${id}`);
85
- if (raw) this.images.set(id, Buffer.from(raw, 'base64'));
89
+ try { this.storage.removeItem(`img:${id}`); } catch {}
86
90
  }
87
91
  }
88
92
 
89
93
  private saveCaptures() {
90
- this.storage.setItem('captures', JSON.stringify([...this.captures.values()]));
94
+ try {
95
+ this.storage.setItem('captures', JSON.stringify([...this.captures.values()]));
96
+ } catch (e) {
97
+ this.console.warn('Could not save captures:', e);
98
+ }
99
+ }
100
+
101
+ private imgPath(id: string): string {
102
+ return path.join(this.imgDir, `${id}.jpg`);
91
103
  }
92
104
 
93
105
  private saveImage(id: string, buf: Buffer) {
94
- this.storage.setItem(`img:${id}`, buf.toString('base64'));
106
+ try {
107
+ fs.writeFileSync(this.imgPath(id), buf);
108
+ } catch (e) {
109
+ this.console.warn(`Could not save image ${id}:`, e);
110
+ }
111
+ }
112
+
113
+ private loadImage(id: string): Buffer | undefined {
114
+ try {
115
+ const p = this.imgPath(id);
116
+ if (fs.existsSync(p)) return fs.readFileSync(p);
117
+ } catch {}
118
+ return undefined;
95
119
  }
96
120
 
97
121
  private deleteCapture(id: string) {
98
- const old = this.storage.getItem(`img:${id}`);
99
- if (old) this.storage.removeItem(`img:${id}`);
122
+ try { fs.unlinkSync(this.imgPath(id)); } catch {}
100
123
  this.captures.delete(id);
101
- this.images.delete(id);
102
124
  this.saveCaptures();
103
125
  }
104
126
 
@@ -249,7 +271,6 @@ class DetectionTrainer extends ScryptedDeviceBase implements Settings, HttpReque
249
271
  };
250
272
 
251
273
  this.captures.set(id, record);
252
- this.images.set(id, jpeg);
253
274
  this.saveImage(id, jpeg);
254
275
  this.saveCaptures();
255
276
 
@@ -264,8 +285,8 @@ class DetectionTrainer extends ScryptedDeviceBase implements Settings, HttpReque
264
285
 
265
286
  // Serve image
266
287
  if (path.startsWith('/img/')) {
267
- const id = path.slice(5);
268
- const img = this.images.get(id);
288
+ const id = path.slice(5).replace(/[^a-zA-Z0-9_\-]/g, ''); // sanitize
289
+ const img = this.loadImage(id);
269
290
  if (!img) return response.send('Not found', { code: 404 });
270
291
  return response.send(img, { headers: { 'Content-Type': 'image/jpeg', 'Cache-Control': 'max-age=3600' } });
271
292
  }
@@ -339,7 +360,7 @@ class DetectionTrainer extends ScryptedDeviceBase implements Settings, HttpReque
339
360
  const dataset: { filename: string; content: string; encoding: string }[] = [];
340
361
 
341
362
  for (const record of labeled) {
342
- const img = this.images.get(record.id);
363
+ const img = this.loadImage(record.id);
343
364
  if (!img) continue;
344
365
 
345
366
  const fname = `${record.id}`;