@clypra/feature-providers 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.
@@ -0,0 +1,16 @@
1
+ {
2
+ "branches": ["main"],
3
+ "plugins": [
4
+ "@semantic-release/commit-analyzer",
5
+ "@semantic-release/release-notes-generator",
6
+ "@semantic-release/npm",
7
+ [
8
+ "@semantic-release/git",
9
+ {
10
+ "assets": ["package.json"],
11
+ "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
12
+ }
13
+ ],
14
+ "@semantic-release/github"
15
+ ]
16
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@clypra/feature-providers",
3
+ "version": "1.0.0",
4
+ "description": "Extensible feature providers for Body Effect Lab",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": "./dist/index.js",
10
+ "./types": "./dist/types.js",
11
+ "./segmentation": "./dist/segmentation/index.js",
12
+ "./chroma-key": "./dist/chroma-key/index.js"
13
+ },
14
+ "scripts": {
15
+ "build": "tsup",
16
+ "dev": "tsup --watch",
17
+ "test": "vitest run",
18
+ "test:watch": "vitest",
19
+ "clean": "rm -rf dist",
20
+ "lint": "tsc --noEmit"
21
+ },
22
+ "dependencies": {},
23
+ "devDependencies": {
24
+ "@types/node": "^22.14.0",
25
+ "tsup": "^8.3.5",
26
+ "typescript": "~5.8.2",
27
+ "vitest": "^3.2.4"
28
+ },
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/AIEraDev/clypra-studio.git",
32
+ "directory": "packages/feature-providers"
33
+ },
34
+ "homepage": "https://github.com/AIEraDev/clypra-studio/tree/main/packages/feature-providers#readme",
35
+ "bugs": {
36
+ "url": "https://github.com/AIEraDev/clypra-studio/issues"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public",
40
+ "registry": "https://registry.npmjs.org/"
41
+ }
42
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * @clypra/feature-providers — Chroma Key Provider
3
+ *
4
+ * Color-based masking (green screen, blue screen, etc.)
5
+ */
6
+
7
+ import type { FeatureProvider, FeatureMap, FeatureMapType, VideoFrame, ProviderConfig } from "../types";
8
+
9
+ /**
10
+ * Chroma Key Provider
11
+ *
12
+ * Extracts masks based on color keying (e.g., green screen removal)
13
+ */
14
+ export class ChromaKeyProvider implements FeatureProvider {
15
+ id = "chroma-key";
16
+ name = "Chroma Key";
17
+ outputs: FeatureMapType[] = ["mask" as FeatureMapType];
18
+
19
+ config: ProviderConfig = {
20
+ keyColor: {
21
+ type: "color",
22
+ default: "#00FF00",
23
+ label: "Key Color",
24
+ },
25
+ threshold: {
26
+ type: "number",
27
+ min: 0,
28
+ max: 1,
29
+ default: 0.4,
30
+ label: "Threshold",
31
+ },
32
+ smoothness: {
33
+ type: "number",
34
+ min: 0,
35
+ max: 1,
36
+ default: 0.2,
37
+ label: "Edge Smoothness",
38
+ },
39
+ };
40
+
41
+ private canvas: HTMLCanvasElement | null = null;
42
+ private ctx: CanvasRenderingContext2D | null = null;
43
+ private keyColor = { r: 0, g: 255, b: 0 };
44
+ private threshold = 0.4;
45
+ private smoothness = 0.2;
46
+
47
+ /**
48
+ * Initialize the provider
49
+ */
50
+ async initialize(): Promise<void> {
51
+ this.canvas = document.createElement("canvas");
52
+ this.ctx = this.canvas.getContext("2d", {
53
+ willReadFrequently: true,
54
+ });
55
+
56
+ if (!this.ctx) {
57
+ throw new Error("Failed to create canvas context");
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Process a video frame
63
+ */
64
+ async process(frame: VideoFrame): Promise<FeatureMap[]> {
65
+ if (!this.canvas || !this.ctx) {
66
+ throw new Error("Provider not initialized");
67
+ }
68
+
69
+ // Get frame dimensions
70
+ const width = frame instanceof HTMLVideoElement ? frame.videoWidth : frame.width;
71
+ const height = frame instanceof HTMLVideoElement ? frame.videoHeight : frame.height;
72
+
73
+ // Resize canvas if needed
74
+ if (this.canvas.width !== width || this.canvas.height !== height) {
75
+ this.canvas.width = width;
76
+ this.canvas.height = height;
77
+ }
78
+
79
+ // Draw frame to canvas
80
+ this.ctx.drawImage(frame as any, 0, 0, width, height);
81
+
82
+ // Get image data
83
+ const imageData = this.ctx.getImageData(0, 0, width, height);
84
+ const data = imageData.data;
85
+
86
+ // Apply chroma key
87
+ for (let i = 0; i < data.length; i += 4) {
88
+ const r = data[i];
89
+ const g = data[i + 1];
90
+ const b = data[i + 2];
91
+
92
+ // Calculate color distance
93
+ const distance = this.colorDistance(r, g, b, this.keyColor.r, this.keyColor.g, this.keyColor.b);
94
+
95
+ // Calculate alpha based on distance
96
+ let alpha = 1.0;
97
+ if (distance < this.threshold) {
98
+ // Within threshold - transparent
99
+ alpha = 0.0;
100
+ } else if (distance < this.threshold + this.smoothness) {
101
+ // Edge smoothing
102
+ alpha = (distance - this.threshold) / this.smoothness;
103
+ }
104
+
105
+ // Set alpha channel (mask)
106
+ data[i + 3] = Math.floor(alpha * 255);
107
+ }
108
+
109
+ // Put processed data back
110
+ this.ctx.putImageData(imageData, 0, 0);
111
+
112
+ return [
113
+ {
114
+ type: "mask" as FeatureMapType,
115
+ data: {
116
+ type: "mask",
117
+ texture: this.canvas,
118
+ isBinary: false,
119
+ inverted: false,
120
+ },
121
+ metadata: {
122
+ keyColor: this.keyColor,
123
+ threshold: this.threshold,
124
+ smoothness: this.smoothness,
125
+ },
126
+ },
127
+ ];
128
+ }
129
+
130
+ /**
131
+ * Clean up resources
132
+ */
133
+ dispose(): void {
134
+ this.canvas = null;
135
+ this.ctx = null;
136
+ }
137
+
138
+ /**
139
+ * Update configuration
140
+ */
141
+ updateConfig(config: Record<string, any>): void {
142
+ if (config.keyColor) {
143
+ this.keyColor = this.hexToRgb(config.keyColor);
144
+ }
145
+ if (config.threshold !== undefined) {
146
+ this.threshold = config.threshold;
147
+ }
148
+ if (config.smoothness !== undefined) {
149
+ this.smoothness = config.smoothness;
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Calculate color distance (Euclidean)
155
+ */
156
+ private colorDistance(r1: number, g1: number, b1: number, r2: number, g2: number, b2: number): number {
157
+ const dr = (r1 - r2) / 255;
158
+ const dg = (g1 - g2) / 255;
159
+ const db = (b1 - b2) / 255;
160
+ return Math.sqrt(dr * dr + dg * dg + db * db) / Math.sqrt(3);
161
+ }
162
+
163
+ /**
164
+ * Convert hex color to RGB
165
+ */
166
+ private hexToRgb(hex: string): { r: number; g: number; b: number } {
167
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
168
+ return result
169
+ ? {
170
+ r: parseInt(result[1], 16),
171
+ g: parseInt(result[2], 16),
172
+ b: parseInt(result[3], 16),
173
+ }
174
+ : { r: 0, g: 255, b: 0 };
175
+ }
176
+ }
package/src/index.ts ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @clypra/feature-providers
3
+ *
4
+ * Extensible feature providers for Body Effect Lab.
5
+ *
6
+ * Feature providers produce feature maps (masks, poses, depth, etc.)
7
+ * that body effects consume. This architecture makes body effects
8
+ * future-proof and infinitely extensible.
9
+ */
10
+
11
+ // Core types
12
+ export * from "./types";
13
+
14
+ // Manager
15
+ export { FeatureProviderManager } from "./manager";
16
+
17
+ // Built-in providers
18
+ export { ChromaKeyProvider } from "./chroma-key";
19
+ export { SegmentationProvider } from "./segmentation";
20
+
21
+ /**
22
+ * Create default provider manager with built-in providers
23
+ */
24
+ import { FeatureProviderManager } from "./manager";
25
+ import { ChromaKeyProvider } from "./chroma-key";
26
+ import { SegmentationProvider } from "./segmentation";
27
+
28
+ export function createDefaultProviderManager(): FeatureProviderManager {
29
+ const manager = new FeatureProviderManager();
30
+
31
+ // Register built-in providers
32
+ manager.register(new ChromaKeyProvider());
33
+ manager.register(new SegmentationProvider());
34
+
35
+ return manager;
36
+ }
package/src/manager.ts ADDED
@@ -0,0 +1,148 @@
1
+ /**
2
+ * @clypra/feature-providers — Provider Manager
3
+ *
4
+ * Manages feature provider lifecycle and orchestrates feature map generation.
5
+ */
6
+
7
+ import type { FeatureProvider, IFeatureProviderManager, FeatureMapType, FeatureMap, VideoFrame } from "./types";
8
+
9
+ /**
10
+ * Feature Provider Manager Implementation
11
+ */
12
+ export class FeatureProviderManager implements IFeatureProviderManager {
13
+ private providers: Map<string, FeatureProvider> = new Map();
14
+ private activeProviders: Set<string> = new Set();
15
+
16
+ /**
17
+ * Register a provider
18
+ */
19
+ register(provider: FeatureProvider): void {
20
+ if (this.providers.has(provider.id)) {
21
+ console.warn(`Provider ${provider.id} is already registered. Replacing.`);
22
+ }
23
+ this.providers.set(provider.id, provider);
24
+ }
25
+
26
+ /**
27
+ * Activate a provider (initialize it)
28
+ */
29
+ async activate(providerId: string): Promise<void> {
30
+ const provider = this.providers.get(providerId);
31
+ if (!provider) {
32
+ throw new Error(`Provider ${providerId} not found`);
33
+ }
34
+
35
+ if (this.activeProviders.has(providerId)) {
36
+ console.warn(`Provider ${providerId} is already active`);
37
+ return;
38
+ }
39
+
40
+ await provider.initialize();
41
+ this.activeProviders.add(providerId);
42
+ }
43
+
44
+ /**
45
+ * Deactivate a provider
46
+ */
47
+ deactivate(providerId: string): void {
48
+ const provider = this.providers.get(providerId);
49
+ if (!provider) {
50
+ console.warn(`Provider ${providerId} not found`);
51
+ return;
52
+ }
53
+
54
+ if (!this.activeProviders.has(providerId)) {
55
+ console.warn(`Provider ${providerId} is not active`);
56
+ return;
57
+ }
58
+
59
+ provider.dispose();
60
+ this.activeProviders.delete(providerId);
61
+ }
62
+
63
+ /**
64
+ * Process frame with all active providers
65
+ */
66
+ async process(frame: VideoFrame): Promise<Map<FeatureMapType, FeatureMap>> {
67
+ const results = new Map<FeatureMapType, FeatureMap>();
68
+
69
+ for (const providerId of this.activeProviders) {
70
+ const provider = this.providers.get(providerId);
71
+ if (!provider) continue;
72
+
73
+ try {
74
+ const maps = await provider.process(frame);
75
+
76
+ // Store feature maps by type
77
+ for (const map of maps) {
78
+ // If multiple providers produce the same type, last one wins
79
+ // In the future, we could have a priority system or merging strategy
80
+ results.set(map.type, map);
81
+ }
82
+ } catch (error) {
83
+ console.error(`Error processing with provider ${providerId}:`, error);
84
+ }
85
+ }
86
+
87
+ return results;
88
+ }
89
+
90
+ /**
91
+ * Get providers that can produce a specific feature type
92
+ */
93
+ getProvidersForFeature(featureType: FeatureMapType): FeatureProvider[] {
94
+ const providers: FeatureProvider[] = [];
95
+
96
+ for (const provider of this.providers.values()) {
97
+ if (provider.outputs.includes(featureType)) {
98
+ providers.push(provider);
99
+ }
100
+ }
101
+
102
+ return providers;
103
+ }
104
+
105
+ /**
106
+ * Get all registered providers
107
+ */
108
+ getAllProviders(): FeatureProvider[] {
109
+ return Array.from(this.providers.values());
110
+ }
111
+
112
+ /**
113
+ * Get active providers
114
+ */
115
+ getActiveProviders(): FeatureProvider[] {
116
+ return Array.from(this.activeProviders)
117
+ .map((id) => this.providers.get(id))
118
+ .filter((p): p is FeatureProvider => p !== undefined);
119
+ }
120
+
121
+ /**
122
+ * Get a specific provider by ID
123
+ */
124
+ getProvider(providerId: string): FeatureProvider | undefined {
125
+ return this.providers.get(providerId);
126
+ }
127
+
128
+ /**
129
+ * Check if a provider is active
130
+ */
131
+ isActive(providerId: string): boolean {
132
+ return this.activeProviders.has(providerId);
133
+ }
134
+
135
+ /**
136
+ * Dispose all providers
137
+ */
138
+ dispose(): void {
139
+ for (const providerId of this.activeProviders) {
140
+ const provider = this.providers.get(providerId);
141
+ if (provider) {
142
+ provider.dispose();
143
+ }
144
+ }
145
+ this.activeProviders.clear();
146
+ this.providers.clear();
147
+ }
148
+ }
@@ -0,0 +1,202 @@
1
+ /**
2
+ * @clypra/feature-providers — Segmentation Provider
3
+ *
4
+ * Person segmentation using MediaPipe or similar.
5
+ * This is a placeholder implementation - actual ML integration will be added later.
6
+ */
7
+
8
+ import type { FeatureProvider, FeatureMap, FeatureMapType, VideoFrame, ProviderConfig } from "../types";
9
+
10
+ /**
11
+ * Segmentation Provider (Placeholder)
12
+ *
13
+ * In production, this would use MediaPipe Selfie Segmentation or similar.
14
+ * For now, it creates a simple center-weighted mask as a placeholder.
15
+ */
16
+ export class SegmentationProvider implements FeatureProvider {
17
+ id = "mediapipe-segmentation";
18
+ name = "Person Segmentation";
19
+ outputs: FeatureMapType[] = ["mask" as FeatureMapType];
20
+
21
+ config: ProviderConfig = {
22
+ quality: {
23
+ type: "select",
24
+ options: ["low", "medium", "high"],
25
+ default: "medium",
26
+ label: "Quality",
27
+ },
28
+ edgeSmoothing: {
29
+ type: "number",
30
+ min: 0,
31
+ max: 1,
32
+ default: 0.5,
33
+ label: "Edge Smoothing",
34
+ },
35
+ };
36
+
37
+ private canvas: HTMLCanvasElement | null = null;
38
+ private ctx: CanvasRenderingContext2D | null = null;
39
+ private quality: "low" | "medium" | "high" = "medium";
40
+ private edgeSmoothing = 0.5;
41
+
42
+ /**
43
+ * Initialize the provider
44
+ *
45
+ * TODO: Load MediaPipe model here
46
+ */
47
+ async initialize(): Promise<void> {
48
+ this.canvas = document.createElement("canvas");
49
+ this.ctx = this.canvas.getContext("2d", {
50
+ willReadFrequently: true,
51
+ });
52
+
53
+ if (!this.ctx) {
54
+ throw new Error("Failed to create canvas context");
55
+ }
56
+
57
+ // TODO: Initialize MediaPipe
58
+ // await this.initializeMediaPipe();
59
+
60
+ console.log("Segmentation provider initialized (placeholder mode)");
61
+ }
62
+
63
+ /**
64
+ * Process a video frame
65
+ *
66
+ * TODO: Use actual segmentation model
67
+ */
68
+ async process(frame: VideoFrame): Promise<FeatureMap[]> {
69
+ if (!this.canvas || !this.ctx) {
70
+ throw new Error("Provider not initialized");
71
+ }
72
+
73
+ // Get frame dimensions
74
+ const width = frame instanceof HTMLVideoElement ? frame.videoWidth : frame.width;
75
+ const height = frame instanceof HTMLVideoElement ? frame.videoHeight : frame.height;
76
+
77
+ // Resize canvas if needed
78
+ if (this.canvas.width !== width || this.canvas.height !== height) {
79
+ this.canvas.width = width;
80
+ this.canvas.height = height;
81
+ }
82
+
83
+ // TODO: Replace with actual segmentation
84
+ // For now, create a simple radial gradient mask as placeholder
85
+ this.createPlaceholderMask(width, height);
86
+
87
+ return [
88
+ {
89
+ type: "mask" as FeatureMapType,
90
+ data: {
91
+ type: "mask",
92
+ texture: this.canvas,
93
+ isBinary: false,
94
+ inverted: false,
95
+ },
96
+ metadata: {
97
+ quality: this.quality,
98
+ edgeSmoothing: this.edgeSmoothing,
99
+ placeholder: true,
100
+ },
101
+ },
102
+ ];
103
+ }
104
+
105
+ /**
106
+ * Clean up resources
107
+ */
108
+ dispose(): void {
109
+ // TODO: Dispose MediaPipe resources
110
+ this.canvas = null;
111
+ this.ctx = null;
112
+ }
113
+
114
+ /**
115
+ * Update configuration
116
+ */
117
+ updateConfig(config: Record<string, any>): void {
118
+ if (config.quality) {
119
+ this.quality = config.quality;
120
+ }
121
+ if (config.edgeSmoothing !== undefined) {
122
+ this.edgeSmoothing = config.edgeSmoothing;
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Create a placeholder mask (radial gradient)
128
+ * This will be replaced with actual segmentation
129
+ */
130
+ private createPlaceholderMask(width: number, height: number): void {
131
+ if (!this.ctx) return;
132
+
133
+ const imageData = this.ctx.createImageData(width, height);
134
+ const data = imageData.data;
135
+
136
+ const centerX = width / 2;
137
+ const centerY = height / 2;
138
+ const maxRadius = Math.min(width, height) * 0.4;
139
+
140
+ for (let y = 0; y < height; y++) {
141
+ for (let x = 0; x < width; x++) {
142
+ const i = (y * width + x) * 4;
143
+
144
+ // Calculate distance from center
145
+ const dx = x - centerX;
146
+ const dy = y - centerY;
147
+ const distance = Math.sqrt(dx * dx + dy * dy);
148
+
149
+ // Calculate alpha based on distance (radial gradient)
150
+ let alpha = 1.0 - Math.min(distance / maxRadius, 1.0);
151
+
152
+ // Apply smoothing
153
+ if (this.edgeSmoothing > 0) {
154
+ alpha = this.smoothStep(alpha, this.edgeSmoothing);
155
+ }
156
+
157
+ // Set RGBA (white with varying alpha)
158
+ data[i] = 255; // R
159
+ data[i + 1] = 255; // G
160
+ data[i + 2] = 255; // B
161
+ data[i + 3] = Math.floor(alpha * 255); // A
162
+ }
163
+ }
164
+
165
+ this.ctx.putImageData(imageData, 0, 0);
166
+ }
167
+
168
+ /**
169
+ * Smooth step function for edge smoothing
170
+ */
171
+ private smoothStep(value: number, smoothness: number): number {
172
+ const t = Math.max(0, Math.min(1, (value - (0.5 - smoothness / 2)) / smoothness));
173
+ return t * t * (3 - 2 * t);
174
+ }
175
+ }
176
+
177
+ /**
178
+ * TODO: Actual MediaPipe integration
179
+ *
180
+ * Example of what the real implementation would look like:
181
+ *
182
+ * import { SelfieSegmentation } from '@mediapipe/selfie_segmentation';
183
+ *
184
+ * async initializeMediaPipe() {
185
+ * this.segmenter = new SelfieSegmentation({
186
+ * locateFile: (file) => {
187
+ * return `https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation/${file}`;
188
+ * }
189
+ * });
190
+ *
191
+ * this.segmenter.setOptions({
192
+ * modelSelection: this.quality === 'high' ? 1 : 0,
193
+ * });
194
+ *
195
+ * await this.segmenter.initialize();
196
+ * }
197
+ *
198
+ * async processWithMediaPipe(frame: VideoFrame): Promise<ImageData> {
199
+ * const results = await this.segmenter.send({ image: frame });
200
+ * return results.segmentationMask;
201
+ * }
202
+ */
package/src/types.ts ADDED
@@ -0,0 +1,195 @@
1
+ /**
2
+ * @clypra/feature-providers — Type Definitions
3
+ *
4
+ * Core types for extensible feature providers used in Body Effect Lab.
5
+ */
6
+
7
+ /**
8
+ * Types of feature maps that providers can produce
9
+ */
10
+ export enum FeatureMapType {
11
+ // Segmentation
12
+ Mask = "mask",
13
+ MultiMask = "multi-mask",
14
+
15
+ // Spatial
16
+ Pose = "pose",
17
+ FaceMesh = "face-mesh",
18
+ Skeleton = "skeleton",
19
+ Hands = "hands",
20
+
21
+ // Depth
22
+ Depth = "depth",
23
+ Normal = "normal",
24
+
25
+ // Motion
26
+ OpticalFlow = "optical-flow",
27
+ Motion = "motion",
28
+
29
+ // Semantic
30
+ Hair = "hair",
31
+ Eyes = "eyes",
32
+ Skin = "skin",
33
+
34
+ // Identity
35
+ PersonID = "person-id",
36
+ }
37
+
38
+ /**
39
+ * Base feature map interface
40
+ */
41
+ export interface FeatureMap {
42
+ type: FeatureMapType;
43
+ data: FeatureMapData;
44
+ metadata?: Record<string, any>;
45
+ }
46
+
47
+ /**
48
+ * Union of all feature map data types
49
+ */
50
+ export type FeatureMapData = MaskData | MultiMaskData | PoseData | DepthData | FlowData | MeshData;
51
+
52
+ /**
53
+ * Mask data (binary or alpha mask)
54
+ */
55
+ export interface MaskData {
56
+ type: "mask";
57
+ texture: HTMLCanvasElement | ImageBitmap | HTMLVideoElement;
58
+ isBinary: boolean;
59
+ inverted?: boolean;
60
+ }
61
+
62
+ /**
63
+ * Multiple masks with IDs (for multi-person scenarios)
64
+ */
65
+ export interface MultiMaskData {
66
+ type: "multi-mask";
67
+ masks: Array<{
68
+ id: string;
69
+ texture: HTMLCanvasElement | ImageBitmap;
70
+ confidence: number;
71
+ }>;
72
+ }
73
+
74
+ /**
75
+ * 2D keypoints (pose, hands, face)
76
+ */
77
+ export interface PoseData {
78
+ type: "pose";
79
+ keypoints: Array<{
80
+ name: string;
81
+ x: number; // Normalized 0-1
82
+ y: number; // Normalized 0-1
83
+ confidence: number;
84
+ visible: boolean;
85
+ }>;
86
+ skeleton?: Array<[string, string]>; // Connections between keypoints
87
+ }
88
+
89
+ /**
90
+ * Depth map data
91
+ */
92
+ export interface DepthData {
93
+ type: "depth";
94
+ texture: HTMLCanvasElement | ImageBitmap;
95
+ minDepth: number;
96
+ maxDepth: number;
97
+ confidence?: HTMLCanvasElement | ImageBitmap;
98
+ }
99
+
100
+ /**
101
+ * Optical flow data
102
+ */
103
+ export interface FlowData {
104
+ type: "optical-flow";
105
+ texture: HTMLCanvasElement | ImageBitmap; // RG = motion vectors
106
+ maxMagnitude: number;
107
+ }
108
+
109
+ /**
110
+ * 3D mesh data (face mesh, etc.)
111
+ */
112
+ export interface MeshData {
113
+ type: "face-mesh";
114
+ vertices: Array<{ x: number; y: number; z: number }>;
115
+ indices: number[];
116
+ uvs?: Array<{ u: number; v: number }>;
117
+ }
118
+
119
+ /**
120
+ * Configuration value types
121
+ */
122
+ export type ConfigValue = { type: "number"; min: number; max: number; default: number; label?: string } | { type: "boolean"; default: boolean; label?: string } | { type: "select"; options: string[]; default: string; label?: string } | { type: "color"; default: string; label?: string };
123
+
124
+ /**
125
+ * Provider configuration schema
126
+ */
127
+ export interface ProviderConfig {
128
+ [key: string]: ConfigValue;
129
+ }
130
+
131
+ /**
132
+ * Video frame input (can be HTMLVideoElement, canvas, or bitmap)
133
+ */
134
+ export type VideoFrame = HTMLVideoElement | HTMLCanvasElement | ImageBitmap;
135
+
136
+ /**
137
+ * Feature Provider interface
138
+ *
139
+ * All feature providers must implement this interface.
140
+ * Providers produce feature maps that body effects consume.
141
+ */
142
+ export interface FeatureProvider {
143
+ /** Unique identifier */
144
+ id: string;
145
+
146
+ /** Human-readable name */
147
+ name: string;
148
+
149
+ /** Feature maps this provider produces */
150
+ outputs: FeatureMapType[];
151
+
152
+ /** Configuration schema */
153
+ config?: ProviderConfig;
154
+
155
+ /** Initialize the provider (load models, allocate resources) */
156
+ initialize(): Promise<void>;
157
+
158
+ /** Process a video frame and produce feature maps */
159
+ process(frame: VideoFrame): Promise<FeatureMap[]>;
160
+
161
+ /** Clean up resources */
162
+ dispose(): void;
163
+
164
+ /** Update configuration */
165
+ updateConfig?(config: Record<string, any>): void;
166
+ }
167
+
168
+ /**
169
+ * Feature provider manager
170
+ */
171
+ export interface IFeatureProviderManager {
172
+ /** Register a provider */
173
+ register(provider: FeatureProvider): void;
174
+
175
+ /** Activate a provider (initialize and make ready) */
176
+ activate(providerId: string): Promise<void>;
177
+
178
+ /** Deactivate a provider */
179
+ deactivate(providerId: string): void;
180
+
181
+ /** Process frame with active providers */
182
+ process(frame: VideoFrame): Promise<Map<FeatureMapType, FeatureMap>>;
183
+
184
+ /** Get providers that can produce a specific feature type */
185
+ getProvidersForFeature(featureType: FeatureMapType): FeatureProvider[];
186
+
187
+ /** Get all registered providers */
188
+ getAllProviders(): FeatureProvider[];
189
+
190
+ /** Get active providers */
191
+ getActiveProviders(): FeatureProvider[];
192
+
193
+ /** Dispose all providers */
194
+ dispose(): void;
195
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "lib": ["ES2022", "DOM"],
6
+ "moduleResolution": "bundler",
7
+ "resolveJsonModule": true,
8
+ "allowJs": true,
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "outDir": "./dist",
17
+ "rootDir": "./src"
18
+ },
19
+ "include": ["src/**/*"],
20
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
21
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig({
4
+ entry: {
5
+ index: "src/index.ts",
6
+ types: "src/types.ts",
7
+ "segmentation/index": "src/segmentation/index.ts",
8
+ "chroma-key/index": "src/chroma-key/index.ts",
9
+ },
10
+ format: ["esm"],
11
+ dts: true,
12
+ sourcemap: true,
13
+ clean: true,
14
+ splitting: false,
15
+ treeshake: true,
16
+ });