@simprints/simface-sdk 0.3.1 → 0.5.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.
@@ -0,0 +1,551 @@
1
+ /**
2
+ * Camera capture service.
3
+ *
4
+ * Uses realtime face guidance with automatic capture when supported and falls
5
+ * back to a simpler manual capture flow when the browser cannot support it.
6
+ */
7
+ import { assessFaceQuality, assessFaceQualityForVideo, getVideoDetector } from './face-detection.js';
8
+ const CAPTURE_DIALOG_Z_INDEX = '2147483647';
9
+ const AUTO_CAPTURE_ANALYSIS_INTERVAL_MS = 180;
10
+ const AUTO_CAPTURE_STABLE_FRAMES = 3;
11
+ /**
12
+ * Opens the device camera and returns a confirmed image Blob, or null if cancelled.
13
+ */
14
+ export async function captureFromCamera() {
15
+ if (prefersNativeCameraCapture()) {
16
+ return captureFromFileInput();
17
+ }
18
+ if (!navigator.mediaDevices?.getUserMedia) {
19
+ throw new Error('In-browser camera capture is not supported in this browser.');
20
+ }
21
+ const mode = (await supportsRealtimeAutoCapture()) ? 'auto' : 'manual';
22
+ return captureFromMediaDevices(mode);
23
+ }
24
+ async function supportsRealtimeAutoCapture() {
25
+ if (typeof window.requestAnimationFrame !== 'function' ||
26
+ typeof window.cancelAnimationFrame !== 'function') {
27
+ return false;
28
+ }
29
+ if (!document.createElement('canvas').getContext('2d')) {
30
+ return false;
31
+ }
32
+ try {
33
+ await getVideoDetector();
34
+ return true;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ async function captureFromMediaDevices(initialMode) {
41
+ let stream;
42
+ try {
43
+ stream = await navigator.mediaDevices.getUserMedia({
44
+ video: { facingMode: { ideal: 'user' } },
45
+ audio: false,
46
+ });
47
+ }
48
+ catch (error) {
49
+ throw new Error(describeCameraError(error));
50
+ }
51
+ let streamStopped = false;
52
+ const stopStream = () => {
53
+ if (streamStopped) {
54
+ return;
55
+ }
56
+ streamStopped = true;
57
+ stream.getTracks().forEach((track) => track.stop());
58
+ };
59
+ return new Promise((resolve, reject) => {
60
+ let settled = false;
61
+ let mode = initialMode;
62
+ let overlay = null;
63
+ let previewUrl = '';
64
+ let previewBlob = null;
65
+ let animationFrameId = null;
66
+ let escapeHandler = null;
67
+ let lastAnalysisTimestamp = 0;
68
+ let stableFrameCount = 0;
69
+ let analysisInFlight = false;
70
+ let previewActive = false;
71
+ let videoReady = false;
72
+ const cleanup = () => {
73
+ if (animationFrameId !== null) {
74
+ window.cancelAnimationFrame(animationFrameId);
75
+ }
76
+ if (escapeHandler) {
77
+ window.removeEventListener('keydown', escapeHandler);
78
+ }
79
+ if (previewUrl) {
80
+ URL.revokeObjectURL(previewUrl);
81
+ }
82
+ stopStream();
83
+ overlay?.remove();
84
+ };
85
+ const finalize = (value, error) => {
86
+ if (settled) {
87
+ return;
88
+ }
89
+ settled = true;
90
+ cleanup();
91
+ if (error) {
92
+ reject(error);
93
+ return;
94
+ }
95
+ resolve(value);
96
+ };
97
+ const renderCaptureMode = (video, mediaContainer, title, copy, feedback, actions, cancelButton, captureButton) => {
98
+ previewActive = false;
99
+ stableFrameCount = 0;
100
+ if (previewUrl) {
101
+ URL.revokeObjectURL(previewUrl);
102
+ previewUrl = '';
103
+ }
104
+ previewBlob = null;
105
+ mediaContainer.replaceChildren(video, createGuideOverlay());
106
+ if (videoReady) {
107
+ resumeVideoPreview(video);
108
+ }
109
+ title.textContent = mode === 'auto' ? 'Center your face' : 'Take a face photo';
110
+ copy.textContent =
111
+ mode === 'auto'
112
+ ? 'Keep your face inside the oval. We will capture automatically when the framing looks good.'
113
+ : 'Line up your face in the oval, then take a photo manually.';
114
+ feedback.textContent =
115
+ mode === 'auto'
116
+ ? 'Looking for a single face in frame...'
117
+ : 'When you are ready, press Take photo.';
118
+ setFeedbackState(feedback, mode === 'auto' ? 'neutral' : 'manual');
119
+ captureButton.style.display = mode === 'manual' ? 'inline-flex' : 'none';
120
+ captureButton.disabled = true;
121
+ actions.replaceChildren(cancelButton, captureButton);
122
+ };
123
+ const renderPreviewMode = (video, mediaContainer, title, copy, feedback, actions, cancelButton, confirmButton, retakeButton, blob, qualityResult) => {
124
+ previewActive = true;
125
+ stableFrameCount = 0;
126
+ if (previewUrl) {
127
+ URL.revokeObjectURL(previewUrl);
128
+ }
129
+ previewUrl = URL.createObjectURL(blob);
130
+ previewBlob = blob;
131
+ const image = document.createElement('img');
132
+ image.alt = 'Captured face preview';
133
+ image.src = previewUrl;
134
+ applyStyles(image, {
135
+ position: 'absolute',
136
+ inset: '0',
137
+ zIndex: '1',
138
+ width: '100%',
139
+ height: '100%',
140
+ objectFit: 'cover',
141
+ });
142
+ mediaContainer.replaceChildren(video, image);
143
+ title.textContent = 'Review your photo';
144
+ copy.textContent =
145
+ qualityResult?.passesQualityChecks === false
146
+ ? 'The capture did not pass the checks. Retake the photo.'
147
+ : 'Confirm this photo or retake it.';
148
+ feedback.textContent = qualityResult?.message ?? 'Review the captured image before continuing.';
149
+ setFeedbackState(feedback, qualityResult
150
+ ? qualityResult.passesQualityChecks
151
+ ? 'success'
152
+ : 'error'
153
+ : 'manual');
154
+ actions.replaceChildren(cancelButton, retakeButton);
155
+ if (qualityResult?.passesQualityChecks !== false) {
156
+ actions.append(confirmButton);
157
+ }
158
+ };
159
+ try {
160
+ overlay = document.createElement('div');
161
+ overlay.setAttribute('data-simface-camera-overlay', 'true');
162
+ overlay.setAttribute('role', 'dialog');
163
+ overlay.setAttribute('aria-modal', 'true');
164
+ applyStyles(overlay, {
165
+ position: 'fixed',
166
+ inset: '0',
167
+ zIndex: CAPTURE_DIALOG_Z_INDEX,
168
+ display: 'flex',
169
+ alignItems: 'center',
170
+ justifyContent: 'center',
171
+ padding: '24px',
172
+ background: 'rgba(15, 23, 42, 0.82)',
173
+ });
174
+ const panel = document.createElement('div');
175
+ applyStyles(panel, {
176
+ width: 'min(100%, 560px)',
177
+ display: 'flex',
178
+ flexDirection: 'column',
179
+ gap: '16px',
180
+ padding: '20px',
181
+ borderRadius: '20px',
182
+ background: '#020617',
183
+ color: '#e2e8f0',
184
+ boxShadow: '0 24px 60px rgba(15, 23, 42, 0.35)',
185
+ });
186
+ const title = document.createElement('h2');
187
+ applyStyles(title, {
188
+ margin: '0',
189
+ fontSize: '1.25rem',
190
+ fontWeight: '700',
191
+ });
192
+ const copy = document.createElement('p');
193
+ applyStyles(copy, {
194
+ margin: '0',
195
+ color: '#cbd5e1',
196
+ fontSize: '0.95rem',
197
+ lineHeight: '1.5',
198
+ });
199
+ const mediaContainer = document.createElement('div');
200
+ applyStyles(mediaContainer, {
201
+ position: 'relative',
202
+ overflow: 'hidden',
203
+ width: '100%',
204
+ aspectRatio: '3 / 4',
205
+ minHeight: '320px',
206
+ borderRadius: '18px',
207
+ background: '#000',
208
+ });
209
+ const video = document.createElement('video');
210
+ video.autoplay = true;
211
+ video.muted = true;
212
+ video.playsInline = true;
213
+ video.srcObject = stream;
214
+ applyStyles(video, {
215
+ width: '100%',
216
+ height: '100%',
217
+ objectFit: 'cover',
218
+ });
219
+ const feedback = document.createElement('div');
220
+ applyStyles(feedback, {
221
+ borderRadius: '14px',
222
+ padding: '12px 14px',
223
+ font: '600 14px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
224
+ });
225
+ const actions = document.createElement('div');
226
+ applyStyles(actions, {
227
+ display: 'flex',
228
+ flexWrap: 'wrap',
229
+ gap: '12px',
230
+ justifyContent: 'flex-end',
231
+ });
232
+ const cancelButton = createActionButton('Cancel', 'secondary');
233
+ cancelButton.dataset.simfaceAction = 'cancel';
234
+ const captureButton = createActionButton('Take photo', 'primary');
235
+ captureButton.dataset.simfaceAction = 'capture';
236
+ captureButton.style.display = mode === 'manual' ? 'inline-flex' : 'none';
237
+ captureButton.disabled = true;
238
+ const confirmButton = createActionButton('Use photo', 'primary');
239
+ confirmButton.dataset.simfaceAction = 'confirm';
240
+ const retakeButton = createActionButton('Retake', 'secondary');
241
+ retakeButton.dataset.simfaceAction = 'retake';
242
+ panel.append(title, copy, mediaContainer, feedback, actions);
243
+ overlay.append(panel);
244
+ document.body.appendChild(overlay);
245
+ renderCaptureMode(video, mediaContainer, title, copy, feedback, actions, cancelButton, captureButton);
246
+ escapeHandler = (event) => {
247
+ if (event.key === 'Escape') {
248
+ finalize(null);
249
+ }
250
+ };
251
+ cancelButton.addEventListener('click', () => finalize(null));
252
+ confirmButton.addEventListener('click', async () => {
253
+ if (!previewBlob) {
254
+ finalize(null, new Error('Failed to confirm the photo.'));
255
+ return;
256
+ }
257
+ finalize(previewBlob);
258
+ });
259
+ retakeButton.addEventListener('click', () => {
260
+ renderCaptureMode(video, mediaContainer, title, copy, feedback, actions, cancelButton, captureButton);
261
+ if (mode === 'manual' && videoReady) {
262
+ captureButton.disabled = false;
263
+ }
264
+ if (mode === 'auto') {
265
+ scheduleAutoCapture();
266
+ }
267
+ });
268
+ captureButton.addEventListener('click', async () => {
269
+ if (previewActive) {
270
+ return;
271
+ }
272
+ try {
273
+ const blob = await captureVideoFrame(video);
274
+ const qualityResult = await assessCapturedBlobSafely(blob);
275
+ renderPreviewMode(video, mediaContainer, title, copy, feedback, actions, cancelButton, confirmButton, retakeButton, blob, qualityResult);
276
+ }
277
+ catch (error) {
278
+ finalize(null, error instanceof Error ? error : new Error('Failed to capture an image.'));
279
+ }
280
+ });
281
+ window.addEventListener('keydown', escapeHandler);
282
+ waitForVideoReady(video)
283
+ .then(() => {
284
+ videoReady = true;
285
+ if (mode === 'manual') {
286
+ captureButton.disabled = false;
287
+ return;
288
+ }
289
+ scheduleAutoCapture();
290
+ })
291
+ .catch((error) => {
292
+ finalize(null, error instanceof Error ? error : new Error('Failed to start the camera preview.'));
293
+ });
294
+ function scheduleAutoCapture() {
295
+ if (settled || previewActive || mode !== 'auto') {
296
+ return;
297
+ }
298
+ animationFrameId = window.requestAnimationFrame(async (timestamp) => {
299
+ if (previewActive ||
300
+ analysisInFlight ||
301
+ timestamp - lastAnalysisTimestamp < AUTO_CAPTURE_ANALYSIS_INTERVAL_MS) {
302
+ scheduleAutoCapture();
303
+ return;
304
+ }
305
+ lastAnalysisTimestamp = timestamp;
306
+ analysisInFlight = true;
307
+ try {
308
+ const qualityResult = await assessFaceQualityForVideo(video, timestamp);
309
+ feedback.textContent = qualityResult.message;
310
+ setFeedbackState(feedback, qualityResult.passesQualityChecks ? 'success' : 'neutral');
311
+ if (qualityResult.passesQualityChecks) {
312
+ stableFrameCount += 1;
313
+ }
314
+ else {
315
+ stableFrameCount = 0;
316
+ }
317
+ if (stableFrameCount >= AUTO_CAPTURE_STABLE_FRAMES) {
318
+ const blob = await captureVideoFrame(video);
319
+ renderPreviewMode(video, mediaContainer, title, copy, feedback, actions, cancelButton, confirmButton, retakeButton, blob, qualityResult);
320
+ return;
321
+ }
322
+ }
323
+ catch (error) {
324
+ mode = 'manual';
325
+ renderCaptureMode(video, mediaContainer, title, copy, feedback, actions, cancelButton, captureButton);
326
+ captureButton.disabled = false;
327
+ feedback.textContent = 'Automatic capture is unavailable in this browser. Use Take photo instead.';
328
+ setFeedbackState(feedback, 'manual');
329
+ }
330
+ finally {
331
+ analysisInFlight = false;
332
+ }
333
+ scheduleAutoCapture();
334
+ });
335
+ }
336
+ }
337
+ catch (error) {
338
+ finalize(null, error instanceof Error ? error : new Error('Failed to open the camera capture UI.'));
339
+ }
340
+ });
341
+ }
342
+ function captureFromFileInput() {
343
+ return new Promise((resolve) => {
344
+ const input = document.createElement('input');
345
+ input.type = 'file';
346
+ input.accept = 'image/*';
347
+ input.capture = 'user';
348
+ input.style.display = 'none';
349
+ input.addEventListener('change', () => {
350
+ const file = input.files?.[0] ?? null;
351
+ cleanup();
352
+ resolve(file);
353
+ });
354
+ const handleFocus = () => {
355
+ setTimeout(() => {
356
+ if (!input.files?.length) {
357
+ cleanup();
358
+ resolve(null);
359
+ }
360
+ }, 500);
361
+ };
362
+ window.addEventListener('focus', handleFocus, { once: true });
363
+ function cleanup() {
364
+ window.removeEventListener('focus', handleFocus);
365
+ input.remove();
366
+ }
367
+ document.body.appendChild(input);
368
+ input.click();
369
+ });
370
+ }
371
+ function prefersNativeCameraCapture() {
372
+ return /WhatsApp/i.test(navigator.userAgent);
373
+ }
374
+ async function waitForVideoReady(video) {
375
+ if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
376
+ await video.play();
377
+ return;
378
+ }
379
+ await new Promise((resolve, reject) => {
380
+ const handleReady = () => {
381
+ cleanup();
382
+ resolve();
383
+ };
384
+ const handleError = () => {
385
+ cleanup();
386
+ reject(new Error('Failed to start the camera preview.'));
387
+ };
388
+ const cleanup = () => {
389
+ video.removeEventListener('loadedmetadata', handleReady);
390
+ video.removeEventListener('error', handleError);
391
+ };
392
+ video.addEventListener('loadedmetadata', handleReady, { once: true });
393
+ video.addEventListener('error', handleError, { once: true });
394
+ });
395
+ await video.play();
396
+ }
397
+ function captureVideoFrame(video) {
398
+ if (!video.videoWidth || !video.videoHeight) {
399
+ return Promise.reject(new Error('Camera preview is not ready yet.'));
400
+ }
401
+ const canvas = document.createElement('canvas');
402
+ canvas.width = video.videoWidth;
403
+ canvas.height = video.videoHeight;
404
+ const context = canvas.getContext('2d');
405
+ if (!context) {
406
+ return Promise.reject(new Error('Failed to initialize camera capture.'));
407
+ }
408
+ context.drawImage(video, 0, 0, canvas.width, canvas.height);
409
+ return new Promise((resolve, reject) => {
410
+ canvas.toBlob((blob) => {
411
+ if (!blob) {
412
+ reject(new Error('Failed to capture an image.'));
413
+ return;
414
+ }
415
+ resolve(blob);
416
+ }, 'image/jpeg', 0.92);
417
+ });
418
+ }
419
+ function resumeVideoPreview(video) {
420
+ void video.play().catch(() => {
421
+ // Ignore resume failures here; capture flow already handles preview startup errors.
422
+ });
423
+ }
424
+ async function assessCapturedBlobSafely(blob) {
425
+ try {
426
+ const image = await blobToImage(blob);
427
+ return await assessFaceQuality(image);
428
+ }
429
+ catch {
430
+ return null;
431
+ }
432
+ }
433
+ function createActionButton(label, variant) {
434
+ const button = document.createElement('button');
435
+ button.type = 'button';
436
+ button.textContent = label;
437
+ applyStyles(button, {
438
+ display: 'inline-flex',
439
+ alignItems: 'center',
440
+ justifyContent: 'center',
441
+ border: 'none',
442
+ borderRadius: '999px',
443
+ padding: '12px 18px',
444
+ font: '600 15px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
445
+ cursor: 'pointer',
446
+ color: variant === 'primary' ? '#fff' : '#0f172a',
447
+ background: variant === 'primary' ? '#2563eb' : '#e2e8f0',
448
+ });
449
+ return button;
450
+ }
451
+ function createGuideOverlay() {
452
+ const wrapper = document.createElement('div');
453
+ applyStyles(wrapper, {
454
+ position: 'absolute',
455
+ inset: '0',
456
+ pointerEvents: 'none',
457
+ });
458
+ const guide = document.createElement('div');
459
+ applyStyles(guide, {
460
+ position: 'absolute',
461
+ left: '50%',
462
+ top: '50%',
463
+ width: '64%',
464
+ height: '76%',
465
+ transform: 'translate(-50%, -50%)',
466
+ borderRadius: '999px',
467
+ border: '3px solid rgba(255, 255, 255, 0.9)',
468
+ boxShadow: '0 0 0 9999px rgba(2, 6, 23, 0.34)',
469
+ });
470
+ wrapper.append(guide);
471
+ return wrapper;
472
+ }
473
+ function setFeedbackState(element, state) {
474
+ switch (state) {
475
+ case 'success':
476
+ applyStyles(element, {
477
+ background: '#dcfce7',
478
+ color: '#166534',
479
+ });
480
+ return;
481
+ case 'error':
482
+ applyStyles(element, {
483
+ background: '#fee2e2',
484
+ color: '#991b1b',
485
+ });
486
+ return;
487
+ case 'manual':
488
+ applyStyles(element, {
489
+ background: '#e0f2fe',
490
+ color: '#0f172a',
491
+ });
492
+ return;
493
+ default:
494
+ applyStyles(element, {
495
+ background: '#e2e8f0',
496
+ color: '#0f172a',
497
+ });
498
+ }
499
+ }
500
+ function applyStyles(element, styles) {
501
+ Object.assign(element.style, styles);
502
+ }
503
+ function describeCameraError(error) {
504
+ if (error instanceof DOMException) {
505
+ switch (error.name) {
506
+ case 'NotAllowedError':
507
+ case 'SecurityError':
508
+ return 'Camera access was denied. Allow camera access and try again.';
509
+ case 'NotFoundError':
510
+ return 'No camera was found on this device.';
511
+ case 'NotReadableError':
512
+ return 'The camera is already in use by another application.';
513
+ default:
514
+ return error.message || 'Failed to access the camera.';
515
+ }
516
+ }
517
+ if (error instanceof Error) {
518
+ return error.message;
519
+ }
520
+ return 'Failed to access the camera.';
521
+ }
522
+ /**
523
+ * Loads a Blob as an HTMLImageElement for face detection analysis.
524
+ */
525
+ export function blobToImage(blob) {
526
+ return new Promise((resolve, reject) => {
527
+ const url = URL.createObjectURL(blob);
528
+ const img = new Image();
529
+ img.onload = () => {
530
+ URL.revokeObjectURL(url);
531
+ resolve(img);
532
+ };
533
+ img.onerror = () => {
534
+ URL.revokeObjectURL(url);
535
+ reject(new Error('Failed to load captured image'));
536
+ };
537
+ img.src = url;
538
+ });
539
+ }
540
+ /**
541
+ * Creates a data URL from a Blob for display in an <img> tag.
542
+ */
543
+ export function blobToDataURL(blob) {
544
+ return new Promise((resolve, reject) => {
545
+ const reader = new FileReader();
546
+ reader.onload = () => resolve(reader.result);
547
+ reader.onerror = () => reject(new Error('Failed to read image'));
548
+ reader.readAsDataURL(blob);
549
+ });
550
+ }
551
+ //# sourceMappingURL=camera.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"camera.js","sourceRoot":"","sources":["../../src/services/camera.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { FaceDetector as MediaPipeFaceDetector } from '@mediapipe/tasks-vision';
2
+ import type { FaceQualityResult } from '../types/index.js';
3
+ export declare function getVideoDetector(): Promise<MediaPipeFaceDetector>;
4
+ /**
5
+ * Assess face quality from a captured image.
6
+ * Checks: face present, single face, face centered, sufficient size.
7
+ */
8
+ export declare function assessFaceQuality(imageElement: HTMLImageElement): Promise<FaceQualityResult>;
9
+ export declare function assessFaceQualityForVideo(videoElement: HTMLVideoElement, timestamp: number): Promise<FaceQualityResult>;
@@ -0,0 +1,69 @@
1
+ import { FaceDetector, FilesetResolver } from '@mediapipe/tasks-vision';
2
+ import { evaluateFaceQuality } from './face-quality.js';
3
+ const WASM_CDN = 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/wasm';
4
+ const MODEL_URL = 'https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/1/blaze_face_short_range.tflite';
5
+ // Minimum confidence to consider a face detected
6
+ const MIN_CONFIDENCE = 0.7;
7
+ let visionInstance = null;
8
+ let imageDetectorInstance = null;
9
+ let videoDetectorInstance = null;
10
+ function getVision() {
11
+ visionInstance ?? (visionInstance = FilesetResolver.forVisionTasks(WASM_CDN));
12
+ return visionInstance;
13
+ }
14
+ function getImageDetector() {
15
+ imageDetectorInstance ?? (imageDetectorInstance = createDetector('IMAGE'));
16
+ return imageDetectorInstance;
17
+ }
18
+ export function getVideoDetector() {
19
+ videoDetectorInstance ?? (videoDetectorInstance = createDetector('VIDEO'));
20
+ return videoDetectorInstance;
21
+ }
22
+ async function createDetector(runningMode) {
23
+ const vision = await getVision();
24
+ return FaceDetector.createFromOptions(vision, {
25
+ baseOptions: { modelAssetPath: MODEL_URL },
26
+ runningMode,
27
+ minDetectionConfidence: MIN_CONFIDENCE,
28
+ });
29
+ }
30
+ /**
31
+ * Assess face quality from a captured image.
32
+ * Checks: face present, single face, face centered, sufficient size.
33
+ */
34
+ export async function assessFaceQuality(imageElement) {
35
+ const detector = await getImageDetector();
36
+ const result = detector.detect(imageElement);
37
+ return evaluateFaceQuality({
38
+ detections: mapDetections(result.detections),
39
+ width: imageElement.naturalWidth,
40
+ height: imageElement.naturalHeight,
41
+ });
42
+ }
43
+ export async function assessFaceQualityForVideo(videoElement, timestamp) {
44
+ const detector = await getVideoDetector();
45
+ const result = detector.detectForVideo(videoElement, timestamp);
46
+ return evaluateFaceQuality({
47
+ detections: mapDetections(result.detections),
48
+ width: videoElement.videoWidth,
49
+ height: videoElement.videoHeight,
50
+ });
51
+ }
52
+ function mapDetections(detections) {
53
+ return detections.map((detection) => ({
54
+ boundingBox: detection.boundingBox
55
+ ? {
56
+ originX: detection.boundingBox.originX,
57
+ originY: detection.boundingBox.originY,
58
+ width: detection.boundingBox.width,
59
+ height: detection.boundingBox.height,
60
+ }
61
+ : undefined,
62
+ confidence: detection.categories[0]?.score ?? 0,
63
+ keypoints: detection.keypoints.map((keypoint) => ({
64
+ x: keypoint.x,
65
+ y: keypoint.y,
66
+ })),
67
+ }));
68
+ }
69
+ //# sourceMappingURL=face-detection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"face-detection.js","sourceRoot":"","sources":["../../src/services/face-detection.ts"],"names":[],"mappings":"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"}