@pexip/media-processor 16.7.1 → 17.1.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/CHANGELOG.md +23 -0
- package/README.md +1 -7
- package/dist/main/audio.d.ts +123 -0
- package/dist/main/audio.js +653 -0
- package/dist/main/benchUtils.d.ts +12 -0
- package/dist/main/benchUtils.js +34 -0
- package/dist/main/generator.d.ts +4 -0
- package/dist/main/generator.js +4 -0
- package/dist/main/index.d.ts +17 -0
- package/dist/main/index.js +17 -0
- package/dist/main/math.d.ts +41 -0
- package/dist/main/math.js +57 -0
- package/dist/main/path.d.ts +102 -0
- package/dist/main/path.js +103 -0
- package/dist/main/process.d.ts +239 -0
- package/dist/main/process.js +364 -0
- package/dist/main/processor.d.ts +5 -0
- package/dist/main/processor.js +4 -0
- package/dist/main/transformer.d.ts +1 -0
- package/dist/main/transformer.js +4 -0
- package/dist/main/tsconfig.tsbuildinfo +1 -0
- package/dist/main/typeGuards.d.ts +5 -0
- package/dist/main/typeGuards.js +24 -0
- package/dist/main/types.d.ts +342 -0
- package/dist/main/types.js +1 -0
- package/dist/main/utils.d.ts +173 -0
- package/dist/main/utils.js +364 -0
- package/dist/main/video/canvasRenderUtils.d.ts +22 -0
- package/dist/main/video/canvasRenderUtils.js +198 -0
- package/dist/main/video/canvasTransform.d.ts +8 -0
- package/dist/main/video/canvasTransform.js +173 -0
- package/dist/main/video/constants.d.ts +10 -0
- package/dist/main/video/constants.js +12 -0
- package/dist/main/video/index.d.ts +9 -0
- package/dist/main/video/index.js +9 -0
- package/dist/main/video/load.d.ts +17 -0
- package/dist/main/video/load.js +47 -0
- package/dist/main/video/segmenters/index.d.ts +1 -0
- package/dist/main/video/segmenters/index.js +1 -0
- package/dist/main/video/segmenters/mediapipe.d.ts +13 -0
- package/dist/main/video/segmenters/mediapipe.js +85 -0
- package/dist/main/video/transformer.d.ts +10 -0
- package/dist/main/video/transformer.js +56 -0
- package/dist/main/video/typeGuards.d.ts +2 -0
- package/dist/main/video/typeGuards.js +13 -0
- package/dist/main/video/types.d.ts +98 -0
- package/dist/main/video/types.js +20 -0
- package/dist/main/video/utils.d.ts +102 -0
- package/dist/main/video/utils.js +476 -0
- package/dist/main/video/video.d.ts +19 -0
- package/dist/main/video/video.js +48 -0
- package/dist/main/video/videoStreamTrackProcessor.d.ts +14 -0
- package/dist/main/video/videoStreamTrackProcessor.js +82 -0
- package/dist/main/visual.d.ts +80 -0
- package/dist/main/visual.js +135 -0
- package/dist/main/workletNodes.d.ts +2 -0
- package/dist/main/workletNodes.js +3 -0
- package/dist/workers/index.d.ts +0 -0
- package/dist/workers/index.js +1 -0
- package/dist/workers/tsconfig.tsbuildinfo +1 -0
- package/dist/worklets/denoise.worklet.d.ts +1 -0
- package/dist/worklets/denoise.worklet.js +1 -2
- package/dist/worklets/tsconfig.tsbuildinfo +1 -0
- package/dist/worklets/types.d.ts +52 -0
- package/dist/worklets/types.js +0 -0
- package/package.json +11 -9
- package/dist/index.d.ts +0 -1129
- package/dist/index.mjs +0 -2441
- package/dist/worklets/denoise.worklet.js.map +0 -7
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { stopStreamTracks } from '../utils';
|
|
2
|
+
import { FOREGROUND_THRESHOLD, BACKGROUND_BLUR_AMOUNT, EDGE_BLUR_AMOUNT, FLIP_HORIZONTAL, PROCESSING_WIDTH, PROCESSING_HEIGHT, } from './constants';
|
|
3
|
+
import { createCanvas } from './utils';
|
|
4
|
+
import { createCanvasRenderUtils } from './canvasRenderUtils';
|
|
5
|
+
const createAssertInRange = (from, to) => (value) => {
|
|
6
|
+
if (value < from || value > to) {
|
|
7
|
+
throw new Error(`Invalid value (${value}) to range [${from}, ${to}]`);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
const assertInRangeFrom0To1 = createAssertInRange(0, 1);
|
|
11
|
+
const assertInRangeFrom0To20 = createAssertInRange(0, 20);
|
|
12
|
+
const NOT_INITED_ERROR_MSG = 'Please call init() method first!';
|
|
13
|
+
export const createTransform = (segmenter, { width = PROCESSING_WIDTH, height = PROCESSING_HEIGHT, foregroundThreshold = FOREGROUND_THRESHOLD, backgroundBlurAmount = BACKGROUND_BLUR_AMOUNT, edgeBlurAmount = EDGE_BLUR_AMOUNT, flipHorizontal = FLIP_HORIZONTAL, effects = 'none', selfManageSegmenter, bgImageUrl, } = {}) => {
|
|
14
|
+
const props = {
|
|
15
|
+
segmenter,
|
|
16
|
+
width,
|
|
17
|
+
height,
|
|
18
|
+
foregroundThreshold,
|
|
19
|
+
backgroundBlurAmount,
|
|
20
|
+
edgeBlurAmount,
|
|
21
|
+
flipHorizontal,
|
|
22
|
+
utils: createCanvasRenderUtils(width, height),
|
|
23
|
+
effects,
|
|
24
|
+
backgroundImage: undefined,
|
|
25
|
+
status: 'created',
|
|
26
|
+
backgroundImageUrl: bgImageUrl,
|
|
27
|
+
};
|
|
28
|
+
const processInput = async (input) => {
|
|
29
|
+
if (props.segmenter.status === 'created' ||
|
|
30
|
+
props.segmenter.status === 'closed') {
|
|
31
|
+
await props.segmenter.open();
|
|
32
|
+
}
|
|
33
|
+
if (props.segmenter.status === 'opening') {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
return await props.segmenter.process(input);
|
|
37
|
+
};
|
|
38
|
+
const loadBackgroundImage = async (url) => {
|
|
39
|
+
if (props.backgroundImageUrl === url && props.backgroundImage) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
props.backgroundImageUrl = url;
|
|
43
|
+
props.backgroundImage = await props.utils.loadBackgroundImage(url);
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
get status() {
|
|
47
|
+
return props.status;
|
|
48
|
+
},
|
|
49
|
+
get width() {
|
|
50
|
+
return props.width;
|
|
51
|
+
},
|
|
52
|
+
get height() {
|
|
53
|
+
return props.height;
|
|
54
|
+
},
|
|
55
|
+
get foregroundThreshold() {
|
|
56
|
+
return props.foregroundThreshold;
|
|
57
|
+
},
|
|
58
|
+
get backgroundBlurAmount() {
|
|
59
|
+
return props.backgroundBlurAmount;
|
|
60
|
+
},
|
|
61
|
+
get edgeBlurAmount() {
|
|
62
|
+
return props.edgeBlurAmount;
|
|
63
|
+
},
|
|
64
|
+
get flipHorizontal() {
|
|
65
|
+
return props.flipHorizontal;
|
|
66
|
+
},
|
|
67
|
+
get effects() {
|
|
68
|
+
return props.effects;
|
|
69
|
+
},
|
|
70
|
+
get backgroundImage() {
|
|
71
|
+
return props.backgroundImage;
|
|
72
|
+
},
|
|
73
|
+
set foregroundThreshold(value) {
|
|
74
|
+
assertInRangeFrom0To1(value);
|
|
75
|
+
props.foregroundThreshold = value;
|
|
76
|
+
},
|
|
77
|
+
set backgroundBlurAmount(value) {
|
|
78
|
+
assertInRangeFrom0To20(value);
|
|
79
|
+
props.backgroundBlurAmount = value;
|
|
80
|
+
},
|
|
81
|
+
set edgeBlurAmount(value) {
|
|
82
|
+
assertInRangeFrom0To20(value);
|
|
83
|
+
props.edgeBlurAmount = value;
|
|
84
|
+
},
|
|
85
|
+
set flipHorizontal(value) {
|
|
86
|
+
props.flipHorizontal = value;
|
|
87
|
+
},
|
|
88
|
+
set effects(value) {
|
|
89
|
+
props.effects = value;
|
|
90
|
+
},
|
|
91
|
+
get backgroundImageUrl() {
|
|
92
|
+
return props.backgroundImageUrl;
|
|
93
|
+
},
|
|
94
|
+
set backgroundImage(canvas) {
|
|
95
|
+
props.backgroundImage = canvas;
|
|
96
|
+
},
|
|
97
|
+
loadBackgroundImage,
|
|
98
|
+
get segmenter() {
|
|
99
|
+
return props.segmenter;
|
|
100
|
+
},
|
|
101
|
+
set segmenter(value) {
|
|
102
|
+
if (value !== props.segmenter) {
|
|
103
|
+
props.segmenter = value;
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
init: async () => {
|
|
107
|
+
props.outputCanvas = createCanvas(width, height);
|
|
108
|
+
if (props.backgroundImageUrl) {
|
|
109
|
+
await loadBackgroundImage(props.backgroundImageUrl);
|
|
110
|
+
}
|
|
111
|
+
props.status = 'opened';
|
|
112
|
+
},
|
|
113
|
+
transform: async (videoFrame, controller) => {
|
|
114
|
+
if (!props.outputCanvas) {
|
|
115
|
+
throw new Error(NOT_INITED_ERROR_MSG);
|
|
116
|
+
}
|
|
117
|
+
switch (props.effects) {
|
|
118
|
+
case 'blur': {
|
|
119
|
+
const image = await props.utils.renderImageToOffScreenCanvas(videoFrame, 'inputCanvas');
|
|
120
|
+
const segmentations = await processInput(image);
|
|
121
|
+
if (props.status === 'closed') {
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
if (props.outputCanvas.height !== height) {
|
|
125
|
+
props.outputCanvas.width = width;
|
|
126
|
+
props.outputCanvas.height = height;
|
|
127
|
+
}
|
|
128
|
+
await props.utils.drawBlurEffect(props.outputCanvas, image, segmentations, props.foregroundThreshold, props.backgroundBlurAmount, props.edgeBlurAmount, props.flipHorizontal);
|
|
129
|
+
controller.enqueue(props.outputCanvas);
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case 'overlay': {
|
|
133
|
+
if (!props.backgroundImage) {
|
|
134
|
+
throw new Error('Please call setBackgroundImage() method first');
|
|
135
|
+
}
|
|
136
|
+
const image = await props.utils.renderImageToOffScreenCanvas(videoFrame, 'inputCanvas');
|
|
137
|
+
const segmentations = await processInput(image);
|
|
138
|
+
if (props.status === 'closed') {
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
if (props.outputCanvas.height !== height) {
|
|
142
|
+
props.outputCanvas.width = width;
|
|
143
|
+
props.outputCanvas.height = height;
|
|
144
|
+
}
|
|
145
|
+
await props.utils.drawOverlayEffect(props.outputCanvas, image, props.backgroundImage, segmentations, props.foregroundThreshold, 0, // No blur for overlay
|
|
146
|
+
props.edgeBlurAmount, props.flipHorizontal);
|
|
147
|
+
controller.enqueue(props.outputCanvas);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case 'none': {
|
|
151
|
+
controller.enqueue(videoFrame);
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
props.status = 'processing';
|
|
156
|
+
},
|
|
157
|
+
close: () => {
|
|
158
|
+
if (!selfManageSegmenter) {
|
|
159
|
+
segmenter.close();
|
|
160
|
+
}
|
|
161
|
+
props.outputCanvas = undefined;
|
|
162
|
+
stopStreamTracks(props.outputStream);
|
|
163
|
+
props.outputStream = undefined;
|
|
164
|
+
props.status = 'closed';
|
|
165
|
+
},
|
|
166
|
+
destroy: async () => {
|
|
167
|
+
if (!selfManageSegmenter) {
|
|
168
|
+
await segmenter.destroy();
|
|
169
|
+
}
|
|
170
|
+
props.status = 'destroyed';
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const PROCESSING_WIDTH = 768;
|
|
2
|
+
export declare const PROCESSING_HEIGHT = 432;
|
|
3
|
+
export declare const FOREGROUND_THRESHOLD = 0.5;
|
|
4
|
+
export declare const BACKGROUND_BLUR_AMOUNT = 3;
|
|
5
|
+
export declare const EDGE_BLUR_AMOUNT = 3;
|
|
6
|
+
export declare const FLIP_HORIZONTAL = false;
|
|
7
|
+
export declare const FRAME_RATE = 20;
|
|
8
|
+
export declare enum AbortReason {
|
|
9
|
+
Close = "close"
|
|
10
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const PROCESSING_WIDTH = 768;
|
|
2
|
+
export const PROCESSING_HEIGHT = 432;
|
|
3
|
+
// Rendering parameters
|
|
4
|
+
export const FOREGROUND_THRESHOLD = 0.5;
|
|
5
|
+
export const BACKGROUND_BLUR_AMOUNT = 3; // Between 1 and 20
|
|
6
|
+
export const EDGE_BLUR_AMOUNT = 3; // Between 0 and 20
|
|
7
|
+
export const FLIP_HORIZONTAL = false;
|
|
8
|
+
export const FRAME_RATE = 20;
|
|
9
|
+
export var AbortReason;
|
|
10
|
+
(function (AbortReason) {
|
|
11
|
+
AbortReason["Close"] = "close";
|
|
12
|
+
})(AbortReason || (AbortReason = {}));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './load';
|
|
2
|
+
export * from './video';
|
|
3
|
+
export * from './types';
|
|
4
|
+
export { createTransform as createCanvasTransform } from './canvasTransform';
|
|
5
|
+
export * from './videoStreamTrackProcessor';
|
|
6
|
+
export { createFrameCallbackRequest } from './utils';
|
|
7
|
+
export * from './segmenters';
|
|
8
|
+
export * from './typeGuards';
|
|
9
|
+
export * from './constants';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './load';
|
|
2
|
+
export * from './video';
|
|
3
|
+
export * from './types';
|
|
4
|
+
export { createTransform as createCanvasTransform } from './canvasTransform';
|
|
5
|
+
export * from './videoStreamTrackProcessor';
|
|
6
|
+
export { createFrameCallbackRequest } from './utils';
|
|
7
|
+
export * from './segmenters';
|
|
8
|
+
export * from './typeGuards';
|
|
9
|
+
export * from './constants';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const loadScript: (path: string, id: string) => Promise<void>;
|
|
2
|
+
export type WasmPaths = [string, string | undefined];
|
|
3
|
+
export declare const loadWasms: (paths: WasmPaths[]) => Promise<void>;
|
|
4
|
+
export declare const loadTfjsCore: (prodMode: boolean) => Promise<unknown>;
|
|
5
|
+
export declare const loadTfjsBackendWebGl: () => Promise<{
|
|
6
|
+
default: typeof import("@tensorflow/tfjs-backend-webgl");
|
|
7
|
+
version_webgl: "4.2.0";
|
|
8
|
+
webgl: {
|
|
9
|
+
forceHalfFloat: typeof import("@tensorflow/tfjs-backend-webgl").forceHalfFloat;
|
|
10
|
+
};
|
|
11
|
+
forceHalfFloat(): void;
|
|
12
|
+
MathBackendWebGL: typeof import("@tensorflow/tfjs-backend-webgl").MathBackendWebGL;
|
|
13
|
+
setWebGLContext: typeof import("@tensorflow/tfjs-backend-webgl").setWebGLContext;
|
|
14
|
+
GPGPUContext: typeof import("@tensorflow/tfjs-backend-webgl").GPGPUContext;
|
|
15
|
+
gpgpu_util: typeof import("@tensorflow/tfjs-backend-webgl/dist/gpgpu_util");
|
|
16
|
+
webgl_util: typeof import("@tensorflow/tfjs-backend-webgl/dist/webgl_util");
|
|
17
|
+
}>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const policy = {
|
|
2
|
+
createScriptURL: (url) => {
|
|
3
|
+
if (new URL(url, document.baseURI).origin !== window.location.origin) {
|
|
4
|
+
throw new Error(`Trying to create script url not on same origin (${url} not on ${window.location.origin})`);
|
|
5
|
+
}
|
|
6
|
+
return url;
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
// Ambient definitions does not seem to be working, so we have to do this :(
|
|
10
|
+
const sameOriginPolicy = typeof window.trustedTypes !== 'undefined' &&
|
|
11
|
+
window.trustedTypes.createPolicy
|
|
12
|
+
? window.trustedTypes.createPolicy('same-origin', policy)
|
|
13
|
+
: policy;
|
|
14
|
+
export const loadScript = (path, id) => new Promise((resolve, reject) => {
|
|
15
|
+
const scriptExist = document.getElementById(id);
|
|
16
|
+
if (scriptExist) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const removeScript = () => {
|
|
20
|
+
document.head.removeChild(script);
|
|
21
|
+
};
|
|
22
|
+
const script = document.createElement('script');
|
|
23
|
+
// Use a cast as typescript dom types do not support it yet.
|
|
24
|
+
script.src = sameOriginPolicy.createScriptURL(path);
|
|
25
|
+
script.id = id;
|
|
26
|
+
script.onload = () => {
|
|
27
|
+
removeScript();
|
|
28
|
+
resolve();
|
|
29
|
+
};
|
|
30
|
+
script.onerror = ev => {
|
|
31
|
+
removeScript();
|
|
32
|
+
reject(ev);
|
|
33
|
+
};
|
|
34
|
+
document.head.appendChild(script);
|
|
35
|
+
});
|
|
36
|
+
export const loadWasms = async (paths) => {
|
|
37
|
+
// TODO: map the wasm path
|
|
38
|
+
await Promise.all(paths.map(([path]) => loadScript(path, path)));
|
|
39
|
+
};
|
|
40
|
+
export const loadTfjsCore = async (prodMode) => {
|
|
41
|
+
const tfjs = await import('@tensorflow/tfjs-core');
|
|
42
|
+
if (prodMode) {
|
|
43
|
+
tfjs.enableProdMode();
|
|
44
|
+
}
|
|
45
|
+
return tfjs;
|
|
46
|
+
};
|
|
47
|
+
export const loadTfjsBackendWebGl = () => import('@tensorflow/tfjs-backend-webgl');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createSegmenter as createMediapipeSegmenter } from './mediapipe';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createSegmenter as createMediapipeSegmenter } from './mediapipe';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Segmenter, AsyncAssets } from '../types';
|
|
2
|
+
export declare const SelfieSegmentationModelTypes: ['general', 'landscape'];
|
|
3
|
+
export type SelfieSegmentationModelType = (typeof SelfieSegmentationModelTypes)[number];
|
|
4
|
+
interface Options extends AsyncAssets {
|
|
5
|
+
modelType: SelfieSegmentationModelType;
|
|
6
|
+
processingWidth: number;
|
|
7
|
+
processingHeight: number;
|
|
8
|
+
gluePath: string;
|
|
9
|
+
selfieMode: boolean;
|
|
10
|
+
prodMode: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare const createSegmenter: (basePath?: string, { modelType, tfjsCoreLoaded, tfjsBackendLoaded, glueLoaded, processingWidth, processingHeight, gluePath, selfieMode, prodMode, }?: Partial<Options>) => Segmenter;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { toSegmentation, createInputImageConvertor, ensure } from '../utils';
|
|
2
|
+
import { createCanvasRenderUtils } from '../canvasRenderUtils';
|
|
3
|
+
import { PROCESSING_WIDTH, PROCESSING_HEIGHT } from '../constants';
|
|
4
|
+
import { loadScript, loadTfjsCore, loadTfjsBackendWebGl } from '../load';
|
|
5
|
+
export const SelfieSegmentationModelTypes = [
|
|
6
|
+
'general',
|
|
7
|
+
'landscape',
|
|
8
|
+
];
|
|
9
|
+
export const createSegmenter = (basePath = '/', { modelType = 'general', tfjsCoreLoaded = false, tfjsBackendLoaded = false, glueLoaded = false, processingWidth = PROCESSING_WIDTH, processingHeight = PROCESSING_HEIGHT, gluePath = '', selfieMode = false, prodMode = true, } = {}) => {
|
|
10
|
+
const props = {
|
|
11
|
+
selfieMode,
|
|
12
|
+
segmentation: [],
|
|
13
|
+
renderUtils: createCanvasRenderUtils(processingWidth, processingHeight),
|
|
14
|
+
status: 'created',
|
|
15
|
+
tfjsCoreLoaded,
|
|
16
|
+
tfjsBackendLoaded,
|
|
17
|
+
glueLoaded,
|
|
18
|
+
};
|
|
19
|
+
const toInputImage = createInputImageConvertor(input => props.renderUtils.drawAndBlurImageOnOffScreenCanvas(input, 0, 'blurredCanvas'));
|
|
20
|
+
const segmentPerson = async (input) => {
|
|
21
|
+
const segmenter = ensure(props.segmenter);
|
|
22
|
+
props.status = 'processing';
|
|
23
|
+
const image = await toInputImage(input);
|
|
24
|
+
await segmenter.send({ image });
|
|
25
|
+
props.status = 'idle';
|
|
26
|
+
};
|
|
27
|
+
return {
|
|
28
|
+
get model() {
|
|
29
|
+
return 'mediapipeSelfie';
|
|
30
|
+
},
|
|
31
|
+
get width() {
|
|
32
|
+
return processingWidth;
|
|
33
|
+
},
|
|
34
|
+
get height() {
|
|
35
|
+
return processingHeight;
|
|
36
|
+
},
|
|
37
|
+
get status() {
|
|
38
|
+
return props.status;
|
|
39
|
+
},
|
|
40
|
+
open: async () => {
|
|
41
|
+
props.status = 'opening';
|
|
42
|
+
// Avoid side-effects from the modules
|
|
43
|
+
if (!props.tfjsCoreLoaded) {
|
|
44
|
+
await loadTfjsCore(prodMode);
|
|
45
|
+
props.tfjsCoreLoaded = true;
|
|
46
|
+
}
|
|
47
|
+
if (!props.tfjsBackendLoaded) {
|
|
48
|
+
await loadTfjsBackendWebGl();
|
|
49
|
+
props.tfjsBackendLoaded = true;
|
|
50
|
+
}
|
|
51
|
+
if (!props.glueLoaded) {
|
|
52
|
+
await loadScript(gluePath, gluePath);
|
|
53
|
+
props.glueLoaded = true;
|
|
54
|
+
}
|
|
55
|
+
props.segmenter = new SelfieSegmentation({
|
|
56
|
+
locateFile: (path) => `${basePath.replace(/\/+$/, '')}/${path}`,
|
|
57
|
+
});
|
|
58
|
+
const modelSelection = modelType === 'landscape' ? 1 : 0;
|
|
59
|
+
props.segmenter.setOptions({
|
|
60
|
+
modelSelection,
|
|
61
|
+
selfieMode: props.selfieMode,
|
|
62
|
+
});
|
|
63
|
+
props.segmenter.onResults(results => {
|
|
64
|
+
props.segmentation = [
|
|
65
|
+
toSegmentation(results.segmentationMask, 'canvasimagesource', () => 'person'),
|
|
66
|
+
];
|
|
67
|
+
});
|
|
68
|
+
await props.segmenter.initialize();
|
|
69
|
+
props.status = 'opened';
|
|
70
|
+
},
|
|
71
|
+
process: async (input) => {
|
|
72
|
+
await segmentPerson(input);
|
|
73
|
+
return props.segmentation;
|
|
74
|
+
},
|
|
75
|
+
close: () => {
|
|
76
|
+
props.segmenter?.reset();
|
|
77
|
+
props.status = 'closed';
|
|
78
|
+
},
|
|
79
|
+
destroy: async () => {
|
|
80
|
+
props.status = 'destroying';
|
|
81
|
+
await props.segmenter?.close();
|
|
82
|
+
props.status = 'destroyed';
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/// <reference types="dom-webcodecs" />
|
|
2
|
+
import type { InputFrame } from './types';
|
|
3
|
+
export declare const nullTransformController: (controller: Pick<TransformStreamDefaultController<CanvasImageSource>, 'enqueue' | 'terminate'>) => TransformStreamDefaultController<CanvasImageSource>;
|
|
4
|
+
export declare const adaptInputFrameTransformer: (transformer: Transformer<InputFrame, InputFrame>) => {
|
|
5
|
+
transform: (frame: VideoFrame, controller: TransformStreamDefaultController<VideoFrame>) => void | PromiseLike<void> | undefined;
|
|
6
|
+
flush?: TransformerFlushCallback<InputFrame> | undefined;
|
|
7
|
+
readableType?: undefined;
|
|
8
|
+
start?: TransformerStartCallback<InputFrame> | undefined;
|
|
9
|
+
writableType?: undefined;
|
|
10
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const wrapTransformController = (videoFrame, controller) => {
|
|
2
|
+
return {
|
|
3
|
+
get desiredSize() {
|
|
4
|
+
return controller.desiredSize;
|
|
5
|
+
},
|
|
6
|
+
enqueue: frame => {
|
|
7
|
+
if (!frame) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (frame instanceof VideoFrame) {
|
|
11
|
+
return controller.enqueue(frame);
|
|
12
|
+
}
|
|
13
|
+
if (('OffscreenCanvas' in window &&
|
|
14
|
+
frame instanceof OffscreenCanvas) ||
|
|
15
|
+
frame instanceof HTMLCanvasElement ||
|
|
16
|
+
frame instanceof HTMLVideoElement ||
|
|
17
|
+
frame instanceof HTMLImageElement ||
|
|
18
|
+
frame instanceof ImageBitmap) {
|
|
19
|
+
const timestamp = videoFrame.timestamp ?? 0;
|
|
20
|
+
videoFrame.close();
|
|
21
|
+
return controller.enqueue(new VideoFrame(frame, { timestamp, alpha: 'discard' }));
|
|
22
|
+
}
|
|
23
|
+
throw new Error('Unexpected input frame');
|
|
24
|
+
},
|
|
25
|
+
error: reason => controller.error(reason),
|
|
26
|
+
terminate: () => controller.terminate(),
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
export const nullTransformController = (controller) => {
|
|
30
|
+
return {
|
|
31
|
+
get desiredSize() {
|
|
32
|
+
return 0;
|
|
33
|
+
},
|
|
34
|
+
enqueue: frame => {
|
|
35
|
+
if (('OffscreenCanvas' in window &&
|
|
36
|
+
frame instanceof OffscreenCanvas) ||
|
|
37
|
+
frame instanceof HTMLCanvasElement ||
|
|
38
|
+
frame instanceof HTMLVideoElement ||
|
|
39
|
+
frame instanceof HTMLImageElement ||
|
|
40
|
+
frame instanceof ImageBitmap) {
|
|
41
|
+
return controller.enqueue(frame);
|
|
42
|
+
}
|
|
43
|
+
throw new Error('Unexpected input frame');
|
|
44
|
+
},
|
|
45
|
+
error: reason => {
|
|
46
|
+
throw new Error(reason);
|
|
47
|
+
},
|
|
48
|
+
terminate: () => controller.terminate(),
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
export const adaptInputFrameTransformer = (transformer) => {
|
|
52
|
+
const transform = (frame, controller) => {
|
|
53
|
+
return transformer.transform?.(frame, wrapTransformController(frame, controller));
|
|
54
|
+
};
|
|
55
|
+
return { ...transformer, transform };
|
|
56
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { RENDER_EFFECTS, SEG_MODELS } from './types';
|
|
2
|
+
export const isRenderEffects = (t) => {
|
|
3
|
+
if (typeof t !== 'string') {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
return RENDER_EFFECTS.some(effect => effect === t);
|
|
7
|
+
};
|
|
8
|
+
export const isSegmentationModel = (t) => {
|
|
9
|
+
if (typeof t !== 'string') {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
return SEG_MODELS.some(model => model === t);
|
|
13
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/// <reference types="dom-webcodecs" />
|
|
2
|
+
import type { Tensor3D } from '@tensorflow/tfjs-core';
|
|
3
|
+
import type { Canvas, Transform } from '../types';
|
|
4
|
+
export declare const RENDER_EFFECTS: readonly ["none", "blur", "overlay"];
|
|
5
|
+
export declare const SEG_MODELS: readonly ["mediapipeSelfie"];
|
|
6
|
+
/**
|
|
7
|
+
* Interfaces from "tensorflow-models/body-segmentation" interfaces
|
|
8
|
+
*/
|
|
9
|
+
export type MaskUnderlyingType = 'canvasimagesource' | 'imagedata' | 'tensor';
|
|
10
|
+
export interface Mask {
|
|
11
|
+
toCanvasImageSource(): Promise<CanvasImageSource>;
|
|
12
|
+
toImageData(): Promise<ImageData>;
|
|
13
|
+
toTensor(): Promise<Tensor3D>;
|
|
14
|
+
getUnderlyingType(): MaskUnderlyingType;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Interfaces from "tensorflow-models/body-segmentation" interfaces
|
|
18
|
+
*/
|
|
19
|
+
export interface Segmentation {
|
|
20
|
+
maskValueToLabel: (maskValue: number) => string;
|
|
21
|
+
mask: Mask;
|
|
22
|
+
}
|
|
23
|
+
export type Color = {
|
|
24
|
+
r: number;
|
|
25
|
+
g: number;
|
|
26
|
+
b: number;
|
|
27
|
+
a: number;
|
|
28
|
+
};
|
|
29
|
+
export type SegmentationModel = (typeof SEG_MODELS)[number];
|
|
30
|
+
export type RenderEffects = (typeof RENDER_EFFECTS)[number];
|
|
31
|
+
export type ProcessInputType = ImageData | HTMLVideoElement | HTMLImageElement | OffscreenCanvas | HTMLCanvasElement | ImageBitmap;
|
|
32
|
+
export type InputFrame = CanvasImageSource | VideoFrame;
|
|
33
|
+
export type ImageType = CanvasImageSource | ProcessInputType | VideoFrame;
|
|
34
|
+
interface ProcessingSize {
|
|
35
|
+
/**
|
|
36
|
+
* Processing Width size
|
|
37
|
+
*/
|
|
38
|
+
width: number;
|
|
39
|
+
/**
|
|
40
|
+
* Processing height size
|
|
41
|
+
*/
|
|
42
|
+
height: number;
|
|
43
|
+
}
|
|
44
|
+
export interface RenderParams extends ProcessingSize {
|
|
45
|
+
/**
|
|
46
|
+
* Range in between 0 and 1
|
|
47
|
+
* @defaultValue `0.5`
|
|
48
|
+
*/
|
|
49
|
+
foregroundThreshold: number;
|
|
50
|
+
/**
|
|
51
|
+
* Range in between 0 and 20
|
|
52
|
+
* @defaultValue `3`
|
|
53
|
+
*/
|
|
54
|
+
backgroundBlurAmount: number;
|
|
55
|
+
/**
|
|
56
|
+
* Range in between 0 and 20
|
|
57
|
+
* @defaultValue `3`
|
|
58
|
+
*/
|
|
59
|
+
edgeBlurAmount: number;
|
|
60
|
+
/**
|
|
61
|
+
* @defaultValue `false`
|
|
62
|
+
*/
|
|
63
|
+
flipHorizontal: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* @defaultValue `none`
|
|
66
|
+
*/
|
|
67
|
+
effects: RenderEffects;
|
|
68
|
+
/**
|
|
69
|
+
* Background image used for background replacement
|
|
70
|
+
*/
|
|
71
|
+
backgroundImage: Canvas | undefined;
|
|
72
|
+
}
|
|
73
|
+
export interface AsyncAssets {
|
|
74
|
+
tfjsCoreLoaded: boolean;
|
|
75
|
+
tfjsBackendLoaded: boolean;
|
|
76
|
+
glueLoaded: boolean;
|
|
77
|
+
}
|
|
78
|
+
export type ProcessStatus = 'created' | 'opened' | 'opening' | 'processing' | 'idle' | 'closed' | 'destroying' | 'destroyed';
|
|
79
|
+
export interface Process {
|
|
80
|
+
status: ProcessStatus;
|
|
81
|
+
open(): Promise<void>;
|
|
82
|
+
close(): void;
|
|
83
|
+
destroy(): Promise<void>;
|
|
84
|
+
}
|
|
85
|
+
export interface Segmenter extends Process, ProcessingSize {
|
|
86
|
+
model: SegmentationModel;
|
|
87
|
+
process(input: ProcessInputType): Promise<Segmentation[]>;
|
|
88
|
+
}
|
|
89
|
+
export interface Detector<T> extends Process {
|
|
90
|
+
detect(input: ProcessInputType): Promise<T>;
|
|
91
|
+
}
|
|
92
|
+
export interface SegmentationParams extends RenderParams {
|
|
93
|
+
segmenter: Segmenter;
|
|
94
|
+
loadBackgroundImage(url: string): Promise<void>;
|
|
95
|
+
backgroundImageUrl?: string;
|
|
96
|
+
}
|
|
97
|
+
export type SegmentationTransform = Transform<InputFrame, InputFrame> & SegmentationParams & Omit<Process, 'open'>;
|
|
98
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const RENDER_EFFECTS = [
|
|
2
|
+
/**
|
|
3
|
+
* Pass through, and thus there is no effects applied
|
|
4
|
+
*/
|
|
5
|
+
'none',
|
|
6
|
+
/**
|
|
7
|
+
* Background blur effects
|
|
8
|
+
*/
|
|
9
|
+
'blur',
|
|
10
|
+
/**
|
|
11
|
+
* Background overlay effects
|
|
12
|
+
*/
|
|
13
|
+
'overlay',
|
|
14
|
+
];
|
|
15
|
+
export const SEG_MODELS = [
|
|
16
|
+
/**
|
|
17
|
+
* Mediapipe's Selfie Segmentation model
|
|
18
|
+
*/
|
|
19
|
+
'mediapipeSelfie',
|
|
20
|
+
];
|