@popmelt.com/core 0.16.0 → 0.17.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 +17 -9
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/adapter-three.d.mts +71 -1
- package/dist/bridge-entry.mjs +2 -2
- package/dist/{chunk-RYUNKCXK.mjs → chunk-D5CD4PIZ.mjs} +1 -1
- package/dist/chunk-RS3XXK26.mjs +2 -0
- package/dist/cli.mjs +36 -36
- package/dist/index.d.mts +10 -4
- package/dist/index.mjs +40 -40
- package/dist/media.d.mts +20 -3
- package/dist/media.mjs +1 -1
- package/dist/plugin-astro.mjs +1 -1
- package/dist/plugin-vite.mjs +1 -1
- package/dist/{server-F4EKHTYF.mjs → server-XGSUCWFZ.mjs} +36 -36
- package/dist/server.mjs +35 -35
- package/dist/{image-adapter-D8L8QumX.d.mts → video-adapter-cDwq4uNP.d.mts} +242 -2
- package/package.json +1 -1
- package/dist/chunk-HS4EVUFK.mjs +0 -2
|
@@ -73,6 +73,28 @@ type ImageObjectContext = {
|
|
|
73
73
|
height: number;
|
|
74
74
|
};
|
|
75
75
|
};
|
|
76
|
+
/** Coordinates refer to the decoded video frame, not its responsive DOM box. */
|
|
77
|
+
type VideoObjectContext = {
|
|
78
|
+
src: string;
|
|
79
|
+
width: number;
|
|
80
|
+
height: number;
|
|
81
|
+
duration: number;
|
|
82
|
+
currentTime: number;
|
|
83
|
+
normalizedBox: {
|
|
84
|
+
x: number;
|
|
85
|
+
y: number;
|
|
86
|
+
width: number;
|
|
87
|
+
height: number;
|
|
88
|
+
};
|
|
89
|
+
box: {
|
|
90
|
+
x: number;
|
|
91
|
+
y: number;
|
|
92
|
+
width: number;
|
|
93
|
+
height: number;
|
|
94
|
+
};
|
|
95
|
+
trackStart: number;
|
|
96
|
+
trackEnd: number;
|
|
97
|
+
};
|
|
76
98
|
type SubjectFeedbackAttachment = {
|
|
77
99
|
/** Zero-based index in this annotation's transported images. */
|
|
78
100
|
cropImageIndex?: number;
|
|
@@ -91,6 +113,7 @@ type ElementInfo = {
|
|
|
91
113
|
reactComponentPath?: string[];
|
|
92
114
|
reactSource?: ReactSourceLocation;
|
|
93
115
|
imageObject?: ImageObjectContext;
|
|
116
|
+
videoObject?: VideoObjectContext;
|
|
94
117
|
/** The real DOM surface, rather than the subject's hidden projection. */
|
|
95
118
|
sourceElement?: ElementInfo;
|
|
96
119
|
context?: string;
|
|
@@ -234,6 +257,12 @@ type PopmeltSubjectBounds = {
|
|
|
234
257
|
width: number;
|
|
235
258
|
height: number;
|
|
236
259
|
};
|
|
260
|
+
type PopmeltSubjectTrackingAnchor = {
|
|
261
|
+
/** Horizontal position normalized within the subject bounds. */
|
|
262
|
+
x: number;
|
|
263
|
+
/** Vertical position normalized within the subject bounds. */
|
|
264
|
+
y: number;
|
|
265
|
+
};
|
|
237
266
|
type PopmeltSubject = {
|
|
238
267
|
key: string;
|
|
239
268
|
label: string;
|
|
@@ -245,6 +274,9 @@ type PopmeltSubject = {
|
|
|
245
274
|
/** Live owning surface; never serialized or included in model context. */
|
|
246
275
|
sourceElement?: Element;
|
|
247
276
|
imageObject?: ImageObjectContext;
|
|
277
|
+
videoObject?: VideoObjectContext;
|
|
278
|
+
/** Optional provider-neutral foreground anchors used by motion tracking. */
|
|
279
|
+
trackingAnchors?: readonly PopmeltSubjectTrackingAnchor[];
|
|
248
280
|
};
|
|
249
281
|
type PopmeltSurfaceSnapshot = {
|
|
250
282
|
element: HTMLCanvasElement;
|
|
@@ -266,12 +298,16 @@ type PopmeltSubjectCreationSession = {
|
|
|
266
298
|
type PopmeltSubjectAdapter = {
|
|
267
299
|
id: string;
|
|
268
300
|
/** Surface ownership; an explicit image adapter replaces Core's default. */
|
|
269
|
-
surfaceKind?: 'image';
|
|
301
|
+
surfaceKind?: 'image' | 'video';
|
|
302
|
+
/** Resolve once the adapter's persisted subject index is available. */
|
|
303
|
+
whenReady?(): Promise<void>;
|
|
270
304
|
hitTest(clientPoint: {
|
|
271
305
|
x: number;
|
|
272
306
|
y: number;
|
|
273
307
|
}): PopmeltSubject | null;
|
|
274
308
|
resolve(key: string): PopmeltSubject | null;
|
|
309
|
+
/** Recreate a missing durable subject from annotation-captured context. */
|
|
310
|
+
restoreSubject?(key: string, elementInfo: ElementInfo): PopmeltSubject | null;
|
|
275
311
|
/** Optional clean subject crop, without Popmelt's overlay. */
|
|
276
312
|
captureSubject?(key: string): Blob | null | Promise<Blob | null>;
|
|
277
313
|
/** Optional bitmap capture for renderer surfaces that DOM cloning cannot read. */
|
|
@@ -285,6 +321,8 @@ type PopmeltSubjectAdapter = {
|
|
|
285
321
|
renameSubject?(key: string, label: string): PopmeltSubject | null;
|
|
286
322
|
/** Replace a subject's viewport bounds, projected back into adapter space. */
|
|
287
323
|
updateSubjectBounds?(key: string, bounds: PopmeltSubjectBounds): PopmeltSubject | null;
|
|
324
|
+
/** Replace optional foreground anchors normalized within subject bounds. */
|
|
325
|
+
updateSubjectTrackingAnchors?(key: string, anchors: readonly PopmeltSubjectTrackingAnchor[]): PopmeltSubject | null;
|
|
288
326
|
/** Remove a user-created subject or hide an adapter-owned subject. */
|
|
289
327
|
removeSubject?(key: string): void;
|
|
290
328
|
};
|
|
@@ -398,6 +436,7 @@ declare class ImageSubjectAdapter implements PopmeltSubjectAdapter {
|
|
|
398
436
|
y: number;
|
|
399
437
|
}): PopmeltSubject | null;
|
|
400
438
|
resolve(key: string): PopmeltSubject | null;
|
|
439
|
+
restoreSubject(key: string, elementInfo: ElementInfo): PopmeltSubject | null;
|
|
401
440
|
private imageObjectContext;
|
|
402
441
|
captureSubject(key: string): Promise<Blob | null>;
|
|
403
442
|
beginCreate(clientPoint: {
|
|
@@ -423,4 +462,205 @@ declare class ImageSubjectAdapter implements PopmeltSubjectAdapter {
|
|
|
423
462
|
}
|
|
424
463
|
declare function createImageSubjectAdapter(options?: ImageSubjectAdapterOptions): ImageSubjectAdapter;
|
|
425
464
|
|
|
426
|
-
|
|
465
|
+
type VideoObjectBox = ImageObjectBox;
|
|
466
|
+
type VideoTrackingAnchor = PopmeltSubjectTrackingAnchor;
|
|
467
|
+
type VideoTrackKeyframe = {
|
|
468
|
+
time: number;
|
|
469
|
+
box: VideoObjectBox;
|
|
470
|
+
score: number;
|
|
471
|
+
source: 'manual' | 'tracked';
|
|
472
|
+
};
|
|
473
|
+
type VideoTrackObservation = {
|
|
474
|
+
time: number;
|
|
475
|
+
box: VideoObjectBox;
|
|
476
|
+
score: number;
|
|
477
|
+
};
|
|
478
|
+
type VideoTrackingAttempt = {
|
|
479
|
+
/** Expand the normal local search crop during reacquisition. */
|
|
480
|
+
searchScale?: number;
|
|
481
|
+
};
|
|
482
|
+
type VideoTrackSegment = {
|
|
483
|
+
/** First media time at which this object is confidently observable. */
|
|
484
|
+
start: number;
|
|
485
|
+
/** Last confidently observable media time. Omitted while the segment is open. */
|
|
486
|
+
end?: number;
|
|
487
|
+
};
|
|
488
|
+
type VideoTrackingSeed = {
|
|
489
|
+
video: HTMLVideoElement;
|
|
490
|
+
time: number;
|
|
491
|
+
box: VideoObjectBox;
|
|
492
|
+
anchors?: readonly VideoTrackingAnchor[];
|
|
493
|
+
};
|
|
494
|
+
type VideoTrackingSession = {
|
|
495
|
+
track(video: HTMLVideoElement, time: number, attempt?: VideoTrackingAttempt): Promise<VideoTrackObservation | null>;
|
|
496
|
+
reseed?(seed: VideoTrackingSeed): void | Promise<void>;
|
|
497
|
+
dispose?(): void | Promise<void>;
|
|
498
|
+
};
|
|
499
|
+
type VideoTrackSmoother = {
|
|
500
|
+
/** Builds one presentation frame from a chronological durable observation. */
|
|
501
|
+
smooth(frame: VideoTrackKeyframe): VideoTrackKeyframe;
|
|
502
|
+
};
|
|
503
|
+
type VideoMotionState = {
|
|
504
|
+
visible: boolean;
|
|
505
|
+
misses: number;
|
|
506
|
+
hits: number;
|
|
507
|
+
lostAt?: number;
|
|
508
|
+
};
|
|
509
|
+
type VideoMotionPresenceDecision = {
|
|
510
|
+
state: VideoMotionState;
|
|
511
|
+
action: 'accept' | 'hold' | 'close' | 'reopen';
|
|
512
|
+
};
|
|
513
|
+
type VideoMotionAttempt = {
|
|
514
|
+
framesPerSecond: number;
|
|
515
|
+
tracking: VideoTrackingAttempt;
|
|
516
|
+
};
|
|
517
|
+
type VideoBackwardAnalysisOptions = {
|
|
518
|
+
source: HTMLVideoElement;
|
|
519
|
+
seedTime: number;
|
|
520
|
+
keyframes: readonly VideoTrackKeyframe[];
|
|
521
|
+
anchors: readonly VideoTrackingAnchor[];
|
|
522
|
+
maximumFramesPerSecond: number;
|
|
523
|
+
cancelled: () => boolean;
|
|
524
|
+
forwardWork: () => Promise<void> | undefined;
|
|
525
|
+
};
|
|
526
|
+
type VideoBackwardAnalysisResult = {
|
|
527
|
+
earliestTime: number;
|
|
528
|
+
keyframes: VideoTrackKeyframe[];
|
|
529
|
+
};
|
|
530
|
+
/**
|
|
531
|
+
* Pixel-derived motion policy supplied by a perception extension. Core invokes
|
|
532
|
+
* it, then stores and renders the resulting durable and presentation tracks.
|
|
533
|
+
*/
|
|
534
|
+
type VideoMotionPolicy = {
|
|
535
|
+
createSmoother(): VideoTrackSmoother;
|
|
536
|
+
attempt(options: {
|
|
537
|
+
state: VideoMotionState;
|
|
538
|
+
time: number;
|
|
539
|
+
maximumFramesPerSecond: number;
|
|
540
|
+
}): VideoMotionAttempt;
|
|
541
|
+
observe(options: {
|
|
542
|
+
state: VideoMotionState;
|
|
543
|
+
time: number;
|
|
544
|
+
observation: VideoTrackObservation | null;
|
|
545
|
+
}): VideoMotionPresenceDecision;
|
|
546
|
+
analyzeBackward(options: VideoBackwardAnalysisOptions): Promise<VideoBackwardAnalysisResult>;
|
|
547
|
+
};
|
|
548
|
+
/** Optional local perception boundary. Core owns tracks and authoring. */
|
|
549
|
+
type VideoTrackingProvider = {
|
|
550
|
+
cacheKey: string;
|
|
551
|
+
configurationKey: string;
|
|
552
|
+
motion: VideoMotionPolicy;
|
|
553
|
+
createSession(seed: VideoTrackingSeed): Promise<VideoTrackingSession>;
|
|
554
|
+
dispose?(): void | Promise<void>;
|
|
555
|
+
};
|
|
556
|
+
type PersistedVideoTrack = {
|
|
557
|
+
id: string;
|
|
558
|
+
label: string;
|
|
559
|
+
origin: 'manual' | 'detected';
|
|
560
|
+
keyframes: readonly VideoTrackKeyframe[];
|
|
561
|
+
/** See-derived display geometry, persisted so Core can replay it alone. */
|
|
562
|
+
presentationKeyframes?: readonly VideoTrackKeyframe[];
|
|
563
|
+
anchors?: readonly VideoTrackingAnchor[];
|
|
564
|
+
/** Explicit visible spans. Missing on schema-v1 tracks, which migrate open-ended. */
|
|
565
|
+
segments?: readonly VideoTrackSegment[];
|
|
566
|
+
/** Furthest media time already covered by forward inference. */
|
|
567
|
+
analyzedThrough?: number;
|
|
568
|
+
createdAt: number;
|
|
569
|
+
updatedAt: number;
|
|
570
|
+
};
|
|
571
|
+
type PersistedVideoAnalysis = {
|
|
572
|
+
schemaVersion: 1 | 2 | 3;
|
|
573
|
+
source: string;
|
|
574
|
+
duration: number;
|
|
575
|
+
tracks: readonly PersistedVideoTrack[];
|
|
576
|
+
updatedAt: number;
|
|
577
|
+
};
|
|
578
|
+
type VideoSubjectStore = {
|
|
579
|
+
load(key: string): PersistedVideoAnalysis | null | Promise<PersistedVideoAnalysis | null>;
|
|
580
|
+
save(key: string, analysis: PersistedVideoAnalysis): void | Promise<void>;
|
|
581
|
+
remove?(key: string): void | Promise<void>;
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
type VideoSubjectAdapterOptions = {
|
|
585
|
+
tracking?: VideoTrackingProvider;
|
|
586
|
+
selector?: string;
|
|
587
|
+
root?: Document | Element;
|
|
588
|
+
observe?: boolean;
|
|
589
|
+
id?: string;
|
|
590
|
+
store?: VideoSubjectStore;
|
|
591
|
+
maxTrackingFramesPerSecond?: number;
|
|
592
|
+
onError?: (error: unknown, video: HTMLVideoElement) => void;
|
|
593
|
+
};
|
|
594
|
+
declare class VideoSubjectAdapter implements PopmeltSubjectAdapter {
|
|
595
|
+
private readonly options;
|
|
596
|
+
readonly surfaceKind: "video";
|
|
597
|
+
readonly id: string;
|
|
598
|
+
private readonly records;
|
|
599
|
+
private readonly videoKeys;
|
|
600
|
+
private readonly metadata;
|
|
601
|
+
private readonly analyzedSources;
|
|
602
|
+
private readonly listeners;
|
|
603
|
+
private readonly frameHandles;
|
|
604
|
+
private readonly persistTimers;
|
|
605
|
+
private readonly writeChains;
|
|
606
|
+
private readonly pendingWrites;
|
|
607
|
+
private readonly observer;
|
|
608
|
+
private refreshQueued;
|
|
609
|
+
private destroyed;
|
|
610
|
+
private manualSequence;
|
|
611
|
+
private activeTrackingKey;
|
|
612
|
+
private readyPromise;
|
|
613
|
+
constructor(options?: VideoSubjectAdapterOptions);
|
|
614
|
+
whenReady(): Promise<void>;
|
|
615
|
+
listSubjects(): readonly PopmeltSubject[];
|
|
616
|
+
refresh(): Promise<void>;
|
|
617
|
+
hitTest(point: {
|
|
618
|
+
x: number;
|
|
619
|
+
y: number;
|
|
620
|
+
}): PopmeltSubject | null;
|
|
621
|
+
resolve(key: string): PopmeltSubject | null;
|
|
622
|
+
captureSubject(key: string): Promise<Blob | null>;
|
|
623
|
+
beginCreate(point: {
|
|
624
|
+
x: number;
|
|
625
|
+
y: number;
|
|
626
|
+
}): PopmeltSubjectCreationSession | null;
|
|
627
|
+
renameSubject(key: string, label: string): PopmeltSubject | null;
|
|
628
|
+
updateSubjectBounds(key: string, bounds: PopmeltSubjectBounds): PopmeltSubject | null;
|
|
629
|
+
updateSubjectTrackingAnchors(key: string, anchors: readonly PopmeltSubjectTrackingAnchor[]): PopmeltSubject | null;
|
|
630
|
+
removeSubject(key: string): void;
|
|
631
|
+
destroy(): Promise<void>;
|
|
632
|
+
private analyze;
|
|
633
|
+
private addVideoListeners;
|
|
634
|
+
private removeVideoListeners;
|
|
635
|
+
private scheduleFrame;
|
|
636
|
+
private trackVideo;
|
|
637
|
+
private startSession;
|
|
638
|
+
private reseed;
|
|
639
|
+
private failTracking;
|
|
640
|
+
private activeRecordFor;
|
|
641
|
+
private activateMostRecentRecord;
|
|
642
|
+
private activateTrackingRecord;
|
|
643
|
+
private scheduleBackwardAnalysis;
|
|
644
|
+
private cancelBackwardAnalysis;
|
|
645
|
+
private runBackwardAnalysis;
|
|
646
|
+
private acceptObservation;
|
|
647
|
+
private openSegment;
|
|
648
|
+
private closeSegment;
|
|
649
|
+
private insertKeyframe;
|
|
650
|
+
private rebuildPresentationTrack;
|
|
651
|
+
private clearLiveFrames;
|
|
652
|
+
private videoObjectContext;
|
|
653
|
+
private replaceVideoRecords;
|
|
654
|
+
private persistVideo;
|
|
655
|
+
private schedulePersistence;
|
|
656
|
+
private flushScheduledPersistence;
|
|
657
|
+
private matchingVideos;
|
|
658
|
+
private videoIdentity;
|
|
659
|
+
private newObjectId;
|
|
660
|
+
private removeVideoRecords;
|
|
661
|
+
private removeVideo;
|
|
662
|
+
private queueRefresh;
|
|
663
|
+
}
|
|
664
|
+
declare function createVideoSubjectAdapter(options?: VideoSubjectAdapterOptions): VideoSubjectAdapter;
|
|
665
|
+
|
|
666
|
+
export { type VideoTrackingAttempt as $, type Annotation as A, type BridgeStatus as B, type ClaudeResponse as C, type DomModification as D, type ElementInfo as E, type PersistedImageSuppression as F, type PersistedVideoAnalysis as G, type PersistedVideoTrack as H, type ImageRecognitionProvider as I, type VideoBackwardAnalysisOptions as J, type VideoBackwardAnalysisResult as K, type VideoMotionAttempt as L, type VideoMotionPolicy as M, type VideoMotionPresenceDecision as N, type OpaqueTargetReference as O, type PopmeltSubjectAdapter as P, type VideoMotionState as Q, type ReactSourceLocation as R, type SubjectFeedbackAttachment as S, type TextModification as T, VideoSubjectAdapter as U, type VideoTrackingProvider as V, type VideoTrackKeyframe as W, type VideoTrackObservation as X, type VideoTrackSegment as Y, type VideoTrackSmoother as Z, type VideoTrackingAnchor as _, type ImageSubjectAdapterOptions as a, type VideoTrackingSeed as a0, type VideoTrackingSession as a1, createBrowserImageSubjectStore as a2, createImageSubjectAdapter as a3, createMemoryImageSubjectStore as a4, createVideoSubjectAdapter as a5, type VideoSubjectAdapterOptions as b, type AnnotationAttachment as c, type StyleModification as d, type SpacingTokenChange as e, type AnnotationType as f, type ImageObjectContext as g, type Point as h, type PopmeltSubject as i, type PopmeltSubjectBounds as j, type PopmeltSubjectCreationSession as k, type PopmeltSubjectTrackingAnchor as l, type PopmeltSurfaceSnapshot as m, type StyleChange as n, type ToolType as o, type VideoObjectContext as p, type ImageObjectBox as q, type VideoSubjectStore as r, type VideoObjectBox as s, type BrowserImageSubjectStoreOptions as t, type ImageContentHasher as u, type ImageObjectObservation as v, ImageSubjectAdapter as w, type ImageSubjectStore as x, type PersistedImageAnalysis as y, type PersistedImageSubject as z };
|
package/package.json
CHANGED
package/dist/chunk-HS4EVUFK.mjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
var b=(r,e=0,t=1)=>Math.min(t,Math.max(e,r));function z(r){let{width:e,height:t}=r.rect,s=Math.max(1,r.naturalWidth),n=Math.max(1,r.naturalHeight),i=Math.min(e/s,t/n),o=Math.max(e/s,t/n);switch(r.objectFit){case"contain":return{width:s*i,height:n*i};case"cover":return{width:s*o,height:n*o};case"none":return{width:s,height:n};case"scale-down":{let a=Math.min(1,i);return{width:s*a,height:n*a}}default:return{width:e,height:t}}}function A(r,e){let t=r.trim().toLowerCase();if(t.endsWith("%")){let s=Number.parseFloat(t);return Number.isFinite(s)?e*(s/100):0}if(t.endsWith("px")){let s=Number.parseFloat(t);return Number.isFinite(s)?s:0}return t==="center"?e/2:t==="right"||t==="bottom"?e:0}function $(r,e,t){let s=r.objectPosition.trim().split(/\s+/),n=s[0]||"50%",i=s[1]||"50%";return{x:A(n,r.rect.width-e),y:A(i,r.rect.height-t)}}function x(r){let e=r.getBoundingClientRect();if(e.width<=0||e.height<=0||r.naturalWidth<=0||r.naturalHeight<=0)return null;let t=r.ownerDocument.defaultView?.getComputedStyle(r)??getComputedStyle(r),s={rect:e,naturalWidth:r.naturalWidth,naturalHeight:r.naturalHeight,objectFit:t.objectFit||"fill",objectPosition:t.objectPosition||"50% 50%"},n=z(s);return{layout:s,rendered:n,position:$(s,n.width,n.height)}}function j(r,e){let t=x(r);if(!t)return null;let{layout:s,rendered:n,position:i}=t;if(e.x<s.rect.left||e.x>s.rect.left+s.rect.width||e.y<s.rect.top||e.y>s.rect.top+s.rect.height)return null;let o=(e.x-s.rect.left-i.x)/n.width,a=(e.y-s.rect.top-i.y)/n.height;return o>=0&&o<=1&&a>=0&&a<=1?{x:o,y:a}:null}function T(r,e){let t=x(r);if(!t)return null;let{layout:s,rendered:n,position:i}=t,o=(e.left-s.rect.left-i.x)/n.width,a=(e.top-s.rect.top-i.y)/n.height,l=(e.left+e.width-s.rect.left-i.x)/n.width,c=(e.top+e.height-s.rect.top-i.y)/n.height,d=b(Math.min(o,l)),u=b(Math.min(a,c)),m=b(Math.max(o,l)),h=b(Math.max(a,c));return m<=d||h<=u?null:{left:d,top:u,width:m-d,height:h-u}}function M(r,e){if(![e.left,e.top,e.width,e.height].every(Number.isFinite))return null;let t=x(r);if(!t)return null;let{layout:s,rendered:n,position:i}=t,{rect:o}=s,a=o.left+i.x+b(e.left)*n.width,l=o.top+i.y+b(e.top)*n.height,c=o.left+i.x+b(e.left+Math.max(0,e.width))*n.width,d=o.top+i.y+b(e.top+Math.max(0,e.height))*n.height,u=Math.max(o.left,a),m=Math.max(o.top,l),h=Math.min(o.left+o.width,c),p=Math.min(o.top+o.height,d);return h<=u||p<=m?null:{left:u,top:m,width:h-u,height:p-m}}var D="popmelt:see:v1";function P(r){if(!r||typeof r!="object")return!1;let e=r;return["left","top","width","height"].every(t=>typeof e[t]=="number"&&Number.isFinite(e[t]))}function W(r){if(!r||typeof r!="object")return!1;let e=r;return typeof e.id=="string"&&typeof e.label=="string"&&typeof e.score=="number"&&(e.origin==="detected"||e.origin==="manual")&&P(e.box)&&(e.detectedBox===void 0||P(e.detectedBox))}function F(r){if(!r||typeof r!="object")return!1;let e=r;return typeof e.detectedLabel=="string"&&P(e.detectedBox)}function B(r){if(!r||typeof r!="object")return!1;let e=r;return e.schemaVersion===1&&typeof e.source=="string"&&typeof e.imageHash=="string"&&typeof e.inferenceKey=="string"&&typeof e.dedupeKey=="string"&&typeof e.updatedAt=="number"&&Array.isArray(e.subjects)&&e.subjects.every(W)&&(e.suppressions===void 0||Array.isArray(e.suppressions)&&e.suppressions.every(F))}function Y(r={}){let e=r.namespace??D,t=()=>r.storage??window.localStorage,s=a=>`${e}:${a}`,n=a=>`${s(a)}:history`,i=a=>{try{let l=t().getItem(s(a));if(!l)return null;let c=JSON.parse(l);return B(c)?c:null}catch{return null}},o=a=>{try{let l=t().getItem(n(a));if(!l)return[];let c=JSON.parse(l);return Array.isArray(c)?c.filter(B).slice(0,10):[]}catch{return[]}};return{load:i,save(a,l){let c=i(a);if(c&&c.imageHash!==l.imageHash){let d=o(a).filter(u=>u.imageHash!==c.imageHash||u.updatedAt!==c.updatedAt);t().setItem(n(a),JSON.stringify([c,...d].slice(0,10)))}t().setItem(s(a),JSON.stringify(l))},remove(a){t().removeItem(s(a)),t().removeItem(n(a))},loadHistory:o}}function G(){let r=new Map,e=new Map;return{load:t=>r.get(t)??null,save:(t,s)=>{let n=r.get(t);if(n&&n.imageHash!==s.imageHash){let i=e.get(t)??[];e.set(t,[n,...i.filter(o=>o.imageHash!==n.imageHash||o.updatedAt!==n.updatedAt)].slice(0,10))}r.set(t,s)},remove:t=>{r.delete(t),e.delete(t)},loadHistory:t=>e.get(t)??[]}}function K(r){return{label:r.label,score:r.score,box:{...r.box}}}var H="img";function L(r){let e=2166136261;for(let t=0;t<r.length;t+=1)e^=r.charCodeAt(t),e=Math.imul(e,16777619);return(e>>>0).toString(36)}function R(r){return new URL(g(r),r.ownerDocument.baseURI).href}async function N(r){let e=R(r);try{let t=await fetch(e,{cache:"force-cache",credentials:"same-origin"});if(!t.ok)throw new Error(`Unable to hash image (${t.status})`);let s=await t.arrayBuffer();if(!globalThis.crypto?.subtle)throw new Error("SubtleCrypto unavailable");let n=await globalThis.crypto.subtle.digest("SHA-256",s);return Array.from(new Uint8Array(n),i=>i.toString(16).padStart(2,"0")).join("")}catch{return`source:${L(e)}:${r.naturalWidth}x${r.naturalHeight}`}}function g(r){return r.currentSrc||r.src||r.getAttribute("src")||""}function U(r){return r.complete&&r.naturalWidth>0&&r.naturalHeight>0?Promise.resolve():new Promise((e,t)=>{let s=()=>{i(),e()},n=()=>{i(),t(new Error(`Unable to load image: ${g(r)}`))},i=()=>{r.removeEventListener("load",s),r.removeEventListener("error",n)};r.addEventListener("load",s,{once:!0}),r.addEventListener("error",n,{once:!0})})}function _(r,e){return{id:e.id,image:r,detection:K(e),detectedLabel:e.detectedLabel,detectedBox:e.detectedBox?{...e.detectedBox}:void 0,boundsOverridden:e.boundsOverridden,manual:e.origin==="manual"}}function q(r){return{id:r.id,label:r.detection.label,detectedLabel:r.detectedLabel,score:r.detection.score,box:{...r.detection.box},detectedBox:r.detectedBox?{...r.detectedBox}:void 0,origin:r.manual?"manual":"detected",boundsOverridden:r.boundsOverridden}}function V(r){return r.origin==="manual"||r.boundsOverridden===!0||!!(r.detectedLabel&&r.label!==r.detectedLabel)}function J(r,e){return e.x>=r.left&&e.x<=r.left+r.width&&e.y>=r.top&&e.y<=r.top+r.height}var O=class{constructor(e={}){this.options=e;this.surfaceKind="image";this.records=new Map;this.imageKeys=new Map;this.analyzedSources=new WeakMap;this.pending=new WeakMap;this.analysisMetadata=new WeakMap;this.suppressions=new WeakMap;this.writeChains=new WeakMap;this.pendingWrites=new Set;this.refreshQueued=!1;this.destroyed=!1;this.manualSequence=0;this.id=e.id??"see:image",this.readyPromise=this.refresh();let t=e.root??document;this.observer=e.observe!==!1&&typeof MutationObserver<"u"?new MutationObserver(()=>this.queueRefresh()):null,this.observer?.observe(t,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["src","srcset","sizes"]})}whenReady(){return this.readyPromise}listSubjects(){let e=[];for(let t of this.records.keys()){let s=this.resolve(t);s&&e.push(s)}return e}async refresh(){if(this.destroyed)return;let e=this.options.root??document,t=this.options.selector??H,s=new Set;e instanceof HTMLImageElement&&e.matches(t)&&s.add(e);for(let n of e.querySelectorAll(t))n instanceof HTMLImageElement&&s.add(n);for(let n of this.imageKeys.keys())(!s.has(n)||!n.isConnected)&&this.removeImage(n);if(this.options.recognition)for(let n of s)await this.analyze(n);else await Promise.all(Array.from(s,n=>this.analyze(n)))}hitTest(e){let t=null;for(let s of this.records.keys()){let n=this.resolve(s),i=this.records.get(s);if(!n||!i||!J(n.bounds,e))continue;let o=n.bounds.width*n.bounds.height;(!t||o<t.area||o===t.area&&i.detection.score>t.score)&&(t={subject:n,area:o,score:i.detection.score})}return t?.subject??null}resolve(e){let t=this.records.get(e);if(!t||!t.image.isConnected)return null;let s=M(t.image,t.detection.box);if(!s)return null;let n=t.image.getBoundingClientRect(),i=Math.round(t.detection.score*1e3)/1e3;return{key:e,label:t.detection.label,sourceElement:t.image,imageObject:this.imageObjectContext(t),bounds:s,surfaceBounds:{left:n.left,top:n.top,width:n.width,height:n.height},context:`${t.detection.label} ${t.manual?"labelled":"detected"} in ${t.image.alt||"image"}`,data:{kind:"image-object",label:t.detection.label,origin:t.manual?"manual":"detected",...t.detectedLabel?{detectedLabel:t.detectedLabel}:{},score:String(i),imageSrc:g(t.image)}}}imageObjectContext(e){let{image:t,detection:{box:s}}=e;return{src:g(t),alt:t.alt,width:t.naturalWidth,height:t.naturalHeight,normalizedBox:{x:s.left,y:s.top,width:s.width,height:s.height},box:{x:s.left*t.naturalWidth,y:s.top*t.naturalHeight,width:s.width*t.naturalWidth,height:s.height*t.naturalHeight}}}async captureSubject(e){let t=this.records.get(e);if(!t?.image.isConnected||!t.image.complete)return null;let{box:s}=this.imageObjectContext(t);if(s.width<=0||s.height<=0)return null;let n=Math.min(1,1024/Math.max(s.width,s.height)),i=document.createElement("canvas");i.width=Math.max(1,Math.round(s.width*n)),i.height=Math.max(1,Math.round(s.height*n));let o=i.getContext("2d");return o?(o.drawImage(t.image,s.x,s.y,s.width,s.height,0,0,i.width,i.height),new Promise((a,l)=>{try{i.toBlob(a,"image/webp",.95)}catch(c){l(c)}})):null}beginCreate(e){let t=this.matchingImages();for(let s=t.length-1;s>=0;s-=1){let n=t[s];if(!n)continue;let i=j(n,e);if(!i)continue;let o=g(n),a=this.imageIdentity(n,o),l=`manual:${this.newObjectId()}`,c=`${a}:${l}`,d={label:"object",score:1,box:{left:i.x,top:i.y,width:0,height:0}},u=!1,m=!1,h=()=>{if(m)return;m=!0,this.records.set(c,{id:l,image:n,detection:d,manual:!0});let p=this.imageKeys.get(n)??new Set;p.add(c),this.imageKeys.set(n,p)};return{key:c,update:p=>{if(u)return null;h();let y=this.clampedImagePoint(n,p);return y?(d.box={left:Math.min(i.x,y.x),top:Math.min(i.y,y.y),width:Math.abs(y.x-i.x),height:Math.abs(y.y-i.y)},this.resolve(c)):this.resolve(c)},commit:()=>u?null:(h(),this.persistImage(n),this.resolve(c)),cancel:()=>{u||(u=!0,m&&this.removeSubject(c))}}}return null}renameSubject(e,t){let s=this.records.get(e),n=t.trim();return!s||!n?null:(!s.manual&&!s.detectedLabel&&(s.detectedLabel=s.detection.label),s.detection.label=n,this.persistImage(s.image),this.resolve(e))}updateSubjectBounds(e,t){let s=this.records.get(e);if(!s)return null;let n=T(s.image,t);return n?(s.detection.box=n,s.boundsOverridden=!0,this.persistImage(s.image),this.resolve(e)):null}removeSubject(e){let t=this.records.get(e);if(!t)return;if(!t.manual){let n=this.suppressions.get(t.image)??[];n.push({detectedLabel:t.detectedLabel??t.detection.label,detectedBox:{...t.detectedBox??t.detection.box}}),this.suppressions.set(t.image,n)}this.records.delete(e);let s=this.imageKeys.get(t.image);s?.delete(e),s?.size===0&&this.imageKeys.delete(t.image),this.persistImage(t.image)}async destroy(){this.destroyed||(this.destroyed=!0,this.observer?.disconnect(),this.records.clear(),this.imageKeys.clear(),await Promise.allSettled(this.pendingWrites),await this.options.recognition?.dispose?.())}queueRefresh(){this.refreshQueued||this.destroyed||(this.refreshQueued=!0,queueMicrotask(()=>{this.refreshQueued=!1,this.readyPromise=this.refresh()}))}async analyze(e){let t=g(e);if(!t||this.analyzedSources.get(e)===t)return;let s=this.pending.get(e);if(s)return s.source===t?s.promise:(await s.promise,this.analyze(e));this.analyzedSources.has(e)&&this.removeImage(e);let n={source:t,promise:Promise.resolve()},i=(async()=>{try{if(await U(e),this.destroyed)return;let o=R(e),a=this.imageIdentity(e,t),l=`${this.id}:${a}`,c=this.options.recognition,d=this.options.store?await this.options.store.load(l):null;if(this.destroyed||g(e)!==t)return;let u=c?.cacheKey??d?.inferenceKey??"",m=c?.configurationKey??d?.dedupeKey??"",h=d?.source===o,p=!!(e.dataset.popmeltMediaId||e.dataset.popmeltSeeId),y=h||p;if(d&&y){let S=new Map(d.subjects.map(f=>[f.id,f]));for(let f of this.imageSubjects(e))S.set(f.id,f);this.replaceImageRecords(e,a,[...S.values()]),this.suppressions.set(e,[...d.suppressions??[]]),this.analysisMetadata.set(e,{storeKey:l,source:o,imageHash:d.imageHash,inferenceKey:d.inferenceKey,dedupeKey:d.dedupeKey})}if(this.options.hydrateOnly){this.analyzedSources.set(e,t);return}let I=this.options.store?await(this.options.imageHasher??N)(e):"ephemeral";if(this.destroyed||g(e)!==t)return;if(!!(d&&y&&d.imageHash===I&&(!c||d.inferenceKey===u&&d.dedupeKey===m))){this.analysisMetadata.set(e,{storeKey:l,source:o,imageHash:I,inferenceKey:u,dedupeKey:m}),this.analyzedSources.set(e,t),await this.saveImage(e);return}let v=y&&d?.imageHash===I;if(d&&!v){let S=new Set(d.subjects.map(w=>w.id)),f=d.subjects.filter(V);this.replaceImageRecords(e,a,[...f,...this.imageSubjects(e).filter(w=>!S.has(w.id))]),this.suppressions.set(e,[])}if(this.analysisMetadata.set(e,{storeKey:l,source:o,imageHash:I,inferenceKey:v?d.inferenceKey:"",dedupeKey:v?d.dedupeKey:""}),!c){this.analyzedSources.set(e,t),await this.saveImage(e);return}let C=await c.detect(e);if(this.destroyed||g(e)!==t)return;let E=this.suppressions.get(e)??[],k=c.reconcile(C,this.imageSubjects(e),E);this.replaceImageRecords(e,a,k),this.suppressions.set(e,[...E]),this.analysisMetadata.set(e,{storeKey:l,source:o,imageHash:I,inferenceKey:u,dedupeKey:m}),this.analyzedSources.set(e,t),await this.saveImage(e)}catch(o){this.options.onError?.(o,e)}finally{this.pending.get(e)===n&&this.pending.delete(e)}})();return n.promise=i,this.pending.set(e,n),i}replaceImageRecords(e,t,s){this.removeImage(e);let n=new Set;for(let i of s){let o=`${t}:${i.id}`;n.add(o),this.records.set(o,_(e,i))}this.imageKeys.set(e,n)}imageIdentity(e,t){if(e.dataset.popmeltMediaId)return e.dataset.popmeltMediaId;if(e.dataset.popmeltSeeId)return e.dataset.popmeltSeeId;let s=new URL(t,e.ownerDocument.baseURI).href,n=this.options.root??e.ownerDocument,i=this.options.selector??H,o=Array.from(n.querySelectorAll(i)).filter(l=>g(l)===t),a=Math.max(0,o.indexOf(e));return`${L(s)}:${a}`}matchingImages(){let e=this.options.root??document,t=this.options.selector??H,s=[];e instanceof HTMLImageElement&&e.matches(t)&&s.push(e);for(let n of e.querySelectorAll(t))n instanceof HTMLImageElement&&s.push(n);return s}clampedImagePoint(e,t){let s=M(e,{left:0,top:0,width:1,height:1});return s?j(e,{x:Math.max(s.left,Math.min(s.left+s.width,t.x)),y:Math.max(s.top,Math.min(s.top+s.height,t.y))}):null}newObjectId(){let e=globalThis.crypto?.randomUUID?.();return e||L(`${Date.now()}:${this.manualSequence++}:${Math.random().toString(36)}`)}persistImage(e){let t=this.persistencePayload(e);if(!t)return Promise.resolve();let n=(this.writeChains.get(e)??Promise.resolve()).catch(()=>{}).then(()=>t.store.save(t.key,t.analysis)).catch(i=>{this.options.onError?.(i,e)});return this.writeChains.set(e,n),this.pendingWrites.add(n),n.finally(()=>this.pendingWrites.delete(n)),n}async saveImage(e){await this.persistImage(e)}imageSubjects(e){return Array.from(this.imageKeys.get(e)??[]).map(t=>this.records.get(t)).filter(t=>!!t).map(q)}persistencePayload(e){let t=this.options.store,s=this.analysisMetadata.get(e);if(!t||!s)return null;let n=this.imageSubjects(e),i={schemaVersion:1,source:s.source,imageHash:s.imageHash,inferenceKey:s.inferenceKey,dedupeKey:s.dedupeKey,subjects:n,suppressions:this.suppressions.get(e)??[],updatedAt:Date.now()};return{store:t,key:s.storeKey,analysis:i}}removeImage(e){for(let t of this.imageKeys.get(e)??[])this.records.delete(t);this.imageKeys.delete(e),this.analyzedSources.delete(e),this.analysisMetadata.delete(e),this.suppressions.delete(e)}};function se(r={}){return new O(r)}export{j as a,T as b,M as c,Y as d,G as e,O as f,se as g};
|