@pexip/media-processor 17.3.0 → 18.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.
- package/CHANGELOG.md +45 -0
- package/dist/common/backends/webgl/blender.d.ts +11 -0
- package/dist/common/backends/webgl/blender.js +104 -0
- package/dist/common/backends/webgl/blur.d.ts +5 -0
- package/dist/common/backends/webgl/blur.js +153 -0
- package/dist/common/backends/webgl/canvas.d.ts +5 -0
- package/dist/common/backends/webgl/canvas.js +77 -0
- package/dist/common/backends/webgl/renderer.d.ts +8 -0
- package/dist/common/backends/webgl/renderer.js +126 -0
- package/dist/common/backends/webgl/shaders/blendFragmentShader.d.ts +1 -0
- package/dist/common/backends/webgl/shaders/blendFragmentShader.js +63 -0
- package/dist/common/backends/webgl/shaders/dualFilterBlurFragmentShader.d.ts +1 -0
- package/dist/common/backends/webgl/shaders/dualFilterBlurFragmentShader.js +45 -0
- package/dist/common/backends/webgl/shaders/textureFragmentShader.d.ts +1 -0
- package/dist/common/backends/webgl/shaders/textureFragmentShader.js +16 -0
- package/dist/common/backends/webgl/shaders/vertexShader.d.ts +1 -0
- package/dist/common/backends/webgl/shaders/vertexShader.js +19 -0
- package/dist/common/backends/webgl/texture.d.ts +6 -0
- package/dist/common/backends/webgl/texture.js +94 -0
- package/dist/common/backends/webgl/types.d.ts +5 -0
- package/dist/common/backends/webgl/types.js +1 -0
- package/dist/common/backends/webgl/webgl.mock.js +8 -0
- package/dist/common/backends/webgl/webglUtils.d.ts +80 -0
- package/dist/common/backends/webgl/webglUtils.js +194 -0
- package/dist/common/canvasRenderUtils.d.ts +62 -0
- package/dist/{main/video → common}/canvasRenderUtils.js +100 -61
- package/dist/common/constants.d.ts +27 -0
- package/dist/common/constants.js +23 -0
- package/dist/common/index.d.ts +9 -0
- package/dist/common/index.js +9 -0
- package/dist/common/m3.d.ts +17 -0
- package/dist/common/m3.js +69 -0
- package/dist/common/tsconfig.tsbuildinfo +1 -0
- package/dist/common/types/media.d.ts +22 -0
- package/dist/common/types/media.js +1 -0
- package/dist/common/types/messageEvents.d.ts +31 -0
- package/dist/common/types/messageEvents.js +1 -0
- package/dist/common/types/processor.d.ts +15 -0
- package/dist/common/types/processor.js +1 -0
- package/dist/common/types/render.d.ts +38 -0
- package/dist/common/types/render.js +1 -0
- package/dist/common/types/segmentation.d.ts +99 -0
- package/dist/common/types/segmentation.js +4 -0
- package/dist/common/types/utils.d.ts +19 -0
- package/dist/common/types/utils.js +1 -0
- package/dist/common/utils.d.ts +35 -0
- package/dist/common/utils.js +129 -0
- package/dist/main/benchUtils.d.ts +3 -2
- package/dist/main/benchUtils.js +7 -6
- package/dist/main/index.d.ts +2 -0
- package/dist/main/index.js +2 -0
- package/dist/main/process.d.ts +1 -11
- package/dist/main/process.js +0 -20
- package/dist/main/tsconfig.tsbuildinfo +1 -1
- package/dist/main/types.d.ts +2 -2
- package/dist/main/video/canvasTransform.d.ts +14 -5
- package/dist/main/video/canvasTransform.js +101 -86
- package/dist/main/video/constants.d.ts +0 -6
- package/dist/main/video/constants.js +0 -6
- package/dist/main/video/index.d.ts +2 -2
- package/dist/main/video/index.js +2 -2
- package/dist/main/video/load.d.ts +0 -14
- package/dist/main/video/load.js +0 -8
- package/dist/main/video/segmenters/mediapipe.d.ts +9 -11
- package/dist/main/video/segmenters/mediapipe.js +127 -77
- package/dist/main/video/typeGuards.d.ts +0 -1
- package/dist/main/video/typeGuards.js +2 -8
- package/dist/main/video/types.d.ts +19 -61
- package/dist/main/video/types.js +1 -20
- package/dist/main/video/utils.d.ts +5 -12
- package/dist/main/video/utils.js +5 -152
- package/dist/main/video/video.js +1 -0
- package/dist/main/video/videoStreamTrackProcessor.js +7 -4
- package/dist/workers/global.d.js +0 -0
- package/dist/workers/mediaWorker.d.ts +1 -0
- package/dist/workers/mediaWorker.js +188 -0
- package/dist/workers/tsconfig.tsbuildinfo +1 -1
- package/dist/worklets/tsconfig.tsbuildinfo +1 -1
- package/package.json +23 -14
- package/dist/main/video/canvasRenderUtils.d.ts +0 -27
- package/dist/main/video/segmenters/index.d.ts +0 -1
- package/dist/main/video/segmenters/index.js +0 -1
- package/dist/workers/index.js +0 -1
- /package/dist/{workers/index.d.ts → common/backends/webgl/webgl.mock.d.ts} +0 -0
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
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, } = {}) => {
|
|
1
|
+
import { PROCESSING_HEIGHT, PROCESSING_WIDTH, FOREGROUND_THRESHOLD, BACKGROUND_BLUR_AMOUNT, EDGE_BLUR_AMOUNT, MASK_COMBINE_RATIO, } from '../../common/constants';
|
|
2
|
+
import { createCanvasRenderUtils } from '../../common/canvasRenderUtils';
|
|
3
|
+
import { clamping } from '../../common/utils';
|
|
4
|
+
const clampInRangeFrom0To1 = clamping(0, 1);
|
|
5
|
+
const clampInRangeFrom0To9 = clamping(0, 9);
|
|
6
|
+
export const createTransform = (segmenter, { width = PROCESSING_WIDTH, height = PROCESSING_HEIGHT, foregroundThreshold = FOREGROUND_THRESHOLD, backgroundBlurAmount = BACKGROUND_BLUR_AMOUNT, edgeBlurAmount = EDGE_BLUR_AMOUNT, maskCombineRatio = MASK_COMBINE_RATIO, backgroundImageUrl = '', effects = 'none', selfManageSegmenter, ...options } = {}) => {
|
|
14
7
|
const props = {
|
|
15
8
|
segmenter,
|
|
16
9
|
width,
|
|
@@ -18,40 +11,67 @@ export const createTransform = (segmenter, { width = PROCESSING_WIDTH, height =
|
|
|
18
11
|
foregroundThreshold,
|
|
19
12
|
backgroundBlurAmount,
|
|
20
13
|
edgeBlurAmount,
|
|
21
|
-
flipHorizontal,
|
|
22
14
|
utils: createCanvasRenderUtils(width, height),
|
|
23
15
|
effects,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
maskCombineRatio,
|
|
17
|
+
backgroundImage: options.backgroundImage,
|
|
18
|
+
status: 'new',
|
|
19
|
+
backgroundImageUrl,
|
|
20
|
+
outputCanvas: options.outputCanvas,
|
|
21
|
+
outputDelegate: options.outputDelegate,
|
|
22
|
+
};
|
|
23
|
+
const toVideoFrameLike = (input) => {
|
|
24
|
+
// Standardize the input
|
|
25
|
+
const image = input instanceof ImageData
|
|
26
|
+
? props.utils.renderImageDataToOffScreenCanvas(input, 'drawImageDataCanvas')
|
|
27
|
+
: input;
|
|
28
|
+
// Pre-precessing the input to resize to the processing size
|
|
29
|
+
const canvas = props.utils.renderImageToOffScreenCanvas(image, 'inputCanvas');
|
|
30
|
+
if ('close' in input) {
|
|
31
|
+
input.close();
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
frame: canvas.transferToImageBitmap(),
|
|
35
|
+
width,
|
|
36
|
+
height,
|
|
37
|
+
timestamp: performance.now(),
|
|
38
|
+
};
|
|
27
39
|
};
|
|
28
40
|
const processInput = async (input) => {
|
|
29
|
-
|
|
41
|
+
const renderOptions = {
|
|
42
|
+
foregroundThreshold: props.foregroundThreshold,
|
|
43
|
+
backgroundBlurAmount: props.backgroundBlurAmount,
|
|
44
|
+
edgeBlurAmount: props.edgeBlurAmount,
|
|
45
|
+
maskCombineRatio: props.maskCombineRatio,
|
|
46
|
+
backgroundImageUrl: props.backgroundImageUrl,
|
|
47
|
+
effects: props.effects,
|
|
48
|
+
};
|
|
49
|
+
if (props.segmenter.status === 'new' ||
|
|
30
50
|
props.segmenter.status === 'closed') {
|
|
31
|
-
await props.segmenter.open(
|
|
51
|
+
await props.segmenter.open({
|
|
52
|
+
output: props.outputDelegate,
|
|
53
|
+
processingWidth: props.width,
|
|
54
|
+
processingHeight: props.height,
|
|
55
|
+
backgroundImage: props.backgroundImage,
|
|
56
|
+
renderOptions,
|
|
57
|
+
});
|
|
32
58
|
}
|
|
33
59
|
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;
|
|
60
|
+
return undefined;
|
|
41
61
|
}
|
|
42
|
-
|
|
43
|
-
|
|
62
|
+
const inputFrame = toVideoFrameLike(input);
|
|
63
|
+
return props.segmenter.process(inputFrame, renderOptions);
|
|
44
64
|
};
|
|
45
65
|
return {
|
|
46
|
-
get status() {
|
|
47
|
-
return props.status;
|
|
48
|
-
},
|
|
49
66
|
get width() {
|
|
50
67
|
return props.width;
|
|
51
68
|
},
|
|
52
69
|
get height() {
|
|
53
70
|
return props.height;
|
|
54
71
|
},
|
|
72
|
+
get status() {
|
|
73
|
+
return props.status;
|
|
74
|
+
},
|
|
55
75
|
get foregroundThreshold() {
|
|
56
76
|
return props.foregroundThreshold;
|
|
57
77
|
},
|
|
@@ -61,40 +81,59 @@ export const createTransform = (segmenter, { width = PROCESSING_WIDTH, height =
|
|
|
61
81
|
get edgeBlurAmount() {
|
|
62
82
|
return props.edgeBlurAmount;
|
|
63
83
|
},
|
|
64
|
-
get flipHorizontal() {
|
|
65
|
-
return props.flipHorizontal;
|
|
66
|
-
},
|
|
67
84
|
get effects() {
|
|
68
85
|
return props.effects;
|
|
69
86
|
},
|
|
70
|
-
get
|
|
71
|
-
return props.
|
|
87
|
+
get maskCombineRatio() {
|
|
88
|
+
return props.maskCombineRatio;
|
|
72
89
|
},
|
|
73
90
|
set foregroundThreshold(value) {
|
|
74
|
-
|
|
75
|
-
props.foregroundThreshold = value;
|
|
91
|
+
props.foregroundThreshold = clampInRangeFrom0To1(value);
|
|
76
92
|
},
|
|
77
93
|
set backgroundBlurAmount(value) {
|
|
78
|
-
|
|
79
|
-
props.backgroundBlurAmount = value;
|
|
94
|
+
props.backgroundBlurAmount = clampInRangeFrom0To9(value);
|
|
80
95
|
},
|
|
81
96
|
set edgeBlurAmount(value) {
|
|
82
|
-
|
|
83
|
-
props.edgeBlurAmount = value;
|
|
84
|
-
},
|
|
85
|
-
set flipHorizontal(value) {
|
|
86
|
-
props.flipHorizontal = value;
|
|
97
|
+
props.edgeBlurAmount = clampInRangeFrom0To9(value);
|
|
87
98
|
},
|
|
88
99
|
set effects(value) {
|
|
89
100
|
props.effects = value;
|
|
90
101
|
},
|
|
102
|
+
set backgroundImageUrl(value) {
|
|
103
|
+
props.backgroundImageUrl = value;
|
|
104
|
+
},
|
|
91
105
|
get backgroundImageUrl() {
|
|
92
106
|
return props.backgroundImageUrl;
|
|
93
107
|
},
|
|
94
|
-
set
|
|
95
|
-
props.
|
|
108
|
+
set maskCombineRatio(value) {
|
|
109
|
+
props.maskCombineRatio = clampInRangeFrom0To1(value);
|
|
110
|
+
},
|
|
111
|
+
get outputCanvas() {
|
|
112
|
+
return props.outputCanvas;
|
|
113
|
+
},
|
|
114
|
+
get backgroundImage() {
|
|
115
|
+
return props.backgroundImage;
|
|
116
|
+
},
|
|
117
|
+
set backgroundImage(image) {
|
|
118
|
+
if (image &&
|
|
119
|
+
image.key !== props.backgroundImage?.key &&
|
|
120
|
+
image.image !== props.backgroundImage?.image) {
|
|
121
|
+
props.backgroundImage = image;
|
|
122
|
+
if (props.segmenter.status !== 'new' &&
|
|
123
|
+
props.segmenter.status !== 'closed') {
|
|
124
|
+
void props.segmenter.update({ backgroundImage: image });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
set outputCanvas(canvas) {
|
|
129
|
+
props.outputCanvas = canvas;
|
|
130
|
+
},
|
|
131
|
+
get outputDelegate() {
|
|
132
|
+
return props.outputDelegate;
|
|
133
|
+
},
|
|
134
|
+
set outputDelegate(canvas) {
|
|
135
|
+
props.outputDelegate = canvas;
|
|
96
136
|
},
|
|
97
|
-
loadBackgroundImage,
|
|
98
137
|
get segmenter() {
|
|
99
138
|
return props.segmenter;
|
|
100
139
|
},
|
|
@@ -103,48 +142,27 @@ export const createTransform = (segmenter, { width = PROCESSING_WIDTH, height =
|
|
|
103
142
|
props.segmenter = value;
|
|
104
143
|
}
|
|
105
144
|
},
|
|
106
|
-
init:
|
|
107
|
-
props.outputCanvas = createCanvas(width, height);
|
|
108
|
-
if (props.backgroundImageUrl) {
|
|
109
|
-
await loadBackgroundImage(props.backgroundImageUrl);
|
|
110
|
-
}
|
|
145
|
+
init: () => {
|
|
111
146
|
props.status = 'opened';
|
|
147
|
+
return Promise.resolve();
|
|
112
148
|
},
|
|
113
149
|
transform: async (videoFrame, controller) => {
|
|
114
|
-
if (!props.outputCanvas) {
|
|
115
|
-
throw new Error(NOT_INITED_ERROR_MSG);
|
|
116
|
-
}
|
|
117
150
|
switch (props.effects) {
|
|
151
|
+
case 'overlay':
|
|
118
152
|
case 'blur': {
|
|
119
|
-
const
|
|
120
|
-
const
|
|
121
|
-
if (props.status === 'closed'
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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') {
|
|
153
|
+
const result = await processInput(videoFrame);
|
|
154
|
+
const processedFrame = props.outputCanvas ?? result?.frame;
|
|
155
|
+
if (props.status === 'closed' ||
|
|
156
|
+
// Avoid processing disposed frame
|
|
157
|
+
!processedFrame ||
|
|
158
|
+
('displayWidth' in processedFrame &&
|
|
159
|
+
processedFrame.displayWidth === 0) ||
|
|
160
|
+
('width' in processedFrame &&
|
|
161
|
+
processedFrame.width === 0)) {
|
|
139
162
|
break;
|
|
140
163
|
}
|
|
141
|
-
|
|
142
|
-
|
|
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);
|
|
164
|
+
controller.enqueue(props.outputCanvas ?? result?.frame);
|
|
165
|
+
result?.frame.close();
|
|
148
166
|
break;
|
|
149
167
|
}
|
|
150
168
|
case 'none': {
|
|
@@ -158,9 +176,6 @@ export const createTransform = (segmenter, { width = PROCESSING_WIDTH, height =
|
|
|
158
176
|
if (!selfManageSegmenter) {
|
|
159
177
|
segmenter.close();
|
|
160
178
|
}
|
|
161
|
-
props.outputCanvas = undefined;
|
|
162
|
-
stopStreamTracks(props.outputStream);
|
|
163
|
-
props.outputStream = undefined;
|
|
164
179
|
props.status = 'closed';
|
|
165
180
|
},
|
|
166
181
|
destroy: async () => {
|
|
@@ -1,9 +1,3 @@
|
|
|
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
1
|
export declare const FRAME_RATE = 20;
|
|
8
2
|
export declare enum AbortReason {
|
|
9
3
|
Close = "close"
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
export const PROCESSING_WIDTH = 768;
|
|
2
|
-
export const PROCESSING_HEIGHT = 432;
|
|
3
1
|
// 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
2
|
export const FRAME_RATE = 20;
|
|
9
3
|
export var AbortReason;
|
|
10
4
|
(function (AbortReason) {
|
|
@@ -3,7 +3,7 @@ export * from './video';
|
|
|
3
3
|
export * from './types';
|
|
4
4
|
export { createTransform as createCanvasTransform } from './canvasTransform';
|
|
5
5
|
export * from './videoStreamTrackProcessor';
|
|
6
|
-
export { createFrameCallbackRequest } from './utils';
|
|
7
|
-
export * from './segmenters';
|
|
6
|
+
export { createFrameCallbackRequest, createCanvas } from './utils';
|
|
8
7
|
export * from './typeGuards';
|
|
9
8
|
export * from './constants';
|
|
9
|
+
export * from './segmenters/mediapipe';
|
package/dist/main/video/index.js
CHANGED
|
@@ -3,7 +3,7 @@ export * from './video';
|
|
|
3
3
|
export * from './types';
|
|
4
4
|
export { createTransform as createCanvasTransform } from './canvasTransform';
|
|
5
5
|
export * from './videoStreamTrackProcessor';
|
|
6
|
-
export { createFrameCallbackRequest } from './utils';
|
|
7
|
-
export * from './segmenters';
|
|
6
|
+
export { createFrameCallbackRequest, createCanvas } from './utils';
|
|
8
7
|
export * from './typeGuards';
|
|
9
8
|
export * from './constants';
|
|
9
|
+
export * from './segmenters/mediapipe';
|
|
@@ -1,17 +1,3 @@
|
|
|
1
1
|
export declare const loadScript: (path: string, id: string) => Promise<void>;
|
|
2
2
|
export type WasmPaths = [string, string | undefined];
|
|
3
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.13.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
|
-
}>;
|
package/dist/main/video/load.js
CHANGED
|
@@ -36,11 +36,3 @@ export const loadWasms = async (paths) => {
|
|
|
36
36
|
// TODO: map the wasm path
|
|
37
37
|
await Promise.all(paths.map(([path]) => loadScript(path, path)));
|
|
38
38
|
};
|
|
39
|
-
export const loadTfjsCore = async (prodMode) => {
|
|
40
|
-
const tfjs = await import('@tensorflow/tfjs-core');
|
|
41
|
-
if (prodMode) {
|
|
42
|
-
tfjs.enableProdMode();
|
|
43
|
-
}
|
|
44
|
-
return tfjs;
|
|
45
|
-
};
|
|
46
|
-
export const loadTfjsBackendWebGl = () => import('@tensorflow/tfjs-backend-webgl');
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import type { Segmenter
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
selfieMode: boolean;
|
|
10
|
-
prodMode: boolean;
|
|
1
|
+
import type { Segmenter } from '../types';
|
|
2
|
+
import type { SegmenterOptions, ImageSegmenterOptions, SegmentationModelAsset } from '../../../common/types/segmentation';
|
|
3
|
+
interface Options extends ImageSegmenterOptions {
|
|
4
|
+
processingWidth?: number;
|
|
5
|
+
processingHeight?: number;
|
|
6
|
+
workerScriptUrl?: URL;
|
|
7
|
+
credentials?: RequestCredentials;
|
|
8
|
+
modelAsset: SegmentationModelAsset;
|
|
11
9
|
}
|
|
12
|
-
export declare const createSegmenter: (basePath
|
|
10
|
+
export declare const createSegmenter: (basePath: SegmenterOptions['basePath'], { processingWidth, processingHeight, workerScriptUrl, credentials, ...options }: Options) => Segmenter;
|
|
13
11
|
export {};
|
|
@@ -1,89 +1,139 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
import { PROCESS_STATUS, PROCESSING_WIDTH, PROCESSING_HEIGHT, } from '../../../common/constants';
|
|
2
|
+
const cloneImageRecord = async (image) => {
|
|
3
|
+
const cloned = await createImageBitmap(image.image);
|
|
4
|
+
return { image: cloned, key: image.key };
|
|
5
|
+
};
|
|
6
|
+
export const createSegmenter = (basePath = '/', { processingWidth = PROCESSING_WIDTH, processingHeight = PROCESSING_HEIGHT, workerScriptUrl = new URL('../../../workers/mediaWorker.js', import.meta.url), credentials = 'same-origin', ...options }) => {
|
|
7
|
+
const props = { status: PROCESS_STATUS.New, modelAsset: options.modelAsset };
|
|
8
|
+
const initWorker = () => {
|
|
9
|
+
const mediaWorker = new Worker(workerScriptUrl, {
|
|
10
|
+
type: 'classic',
|
|
11
|
+
credentials,
|
|
12
|
+
});
|
|
13
|
+
mediaWorker.addEventListener('error', error => {
|
|
14
|
+
throw error.error;
|
|
15
|
+
});
|
|
16
|
+
mediaWorker.addEventListener('message', (event) => {
|
|
17
|
+
const { type } = event.data;
|
|
18
|
+
if (type === 'error') {
|
|
19
|
+
throw event.data.error;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
return mediaWorker;
|
|
23
|
+
};
|
|
24
|
+
async function postMsg(expectedResponseType, message, transfer) {
|
|
25
|
+
if (!expectedResponseType) {
|
|
26
|
+
return props.mediaWorker?.postMessage(message, transfer);
|
|
27
|
+
}
|
|
28
|
+
return await new Promise((resolve, reject) => {
|
|
29
|
+
const handleMessage = (ev) => {
|
|
30
|
+
removeListener();
|
|
31
|
+
switch (ev.data.type) {
|
|
32
|
+
case expectedResponseType: {
|
|
33
|
+
// @ts-expect-error --- Call `resolve` is expecting to pass an argument or nothing
|
|
34
|
+
resolve(ev.data.data);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
case 'error': {
|
|
38
|
+
reject(ev.data.error);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
default: {
|
|
42
|
+
throw new Error(`UnhandledEvent: ${ev.data.type}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const handleError = (error) => {
|
|
47
|
+
removeListener();
|
|
48
|
+
reject(error.error);
|
|
49
|
+
};
|
|
50
|
+
const removeListener = () => {
|
|
51
|
+
props.mediaWorker?.removeEventListener('message', handleMessage);
|
|
52
|
+
props.mediaWorker?.removeEventListener('error', handleError);
|
|
53
|
+
};
|
|
54
|
+
props.mediaWorker?.addEventListener('message', handleMessage);
|
|
55
|
+
props.mediaWorker?.addEventListener('error', handleError);
|
|
56
|
+
props.mediaWorker?.postMessage(message, transfer);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const open = async (processorOptions) => {
|
|
60
|
+
props.status = PROCESS_STATUS.Opening;
|
|
61
|
+
props.mediaWorker = initWorker();
|
|
62
|
+
props.output = processorOptions?.output;
|
|
63
|
+
const backgroundImage = processorOptions?.backgroundImage &&
|
|
64
|
+
(await cloneImageRecord(processorOptions.backgroundImage));
|
|
65
|
+
await postMsg('opened', {
|
|
66
|
+
type: 'open',
|
|
67
|
+
data: {
|
|
68
|
+
basePath,
|
|
69
|
+
processingWidth,
|
|
70
|
+
processingHeight,
|
|
71
|
+
...processorOptions,
|
|
72
|
+
imageSegmenterOptions: {
|
|
73
|
+
...options,
|
|
74
|
+
...processorOptions?.imageSegmenterOptions,
|
|
75
|
+
},
|
|
76
|
+
backgroundImage,
|
|
77
|
+
},
|
|
78
|
+
}, [processorOptions?.output, backgroundImage?.image].filter(Boolean));
|
|
79
|
+
props.status = PROCESS_STATUS.Opened;
|
|
80
|
+
};
|
|
81
|
+
const process = async (input, options) => {
|
|
82
|
+
props.status = PROCESS_STATUS.Processing;
|
|
83
|
+
const resultFrame = await postMsg('processed', {
|
|
84
|
+
type: 'process',
|
|
85
|
+
data: { videoFrame: input, renderOptions: options },
|
|
86
|
+
}, [input.frame]);
|
|
87
|
+
if (props.status === PROCESS_STATUS.Processing) {
|
|
88
|
+
props.status = PROCESS_STATUS.Idle;
|
|
89
|
+
}
|
|
90
|
+
return resultFrame;
|
|
91
|
+
};
|
|
92
|
+
const update = async (options) => {
|
|
93
|
+
const backgroundImage = options.backgroundImage &&
|
|
94
|
+
(await cloneImageRecord(options.backgroundImage));
|
|
95
|
+
const transfer = backgroundImage ? [backgroundImage.image] : [];
|
|
96
|
+
await postMsg(undefined, {
|
|
97
|
+
type: 'update',
|
|
98
|
+
data: {
|
|
99
|
+
...options,
|
|
100
|
+
backgroundImage,
|
|
101
|
+
},
|
|
102
|
+
}, transfer);
|
|
18
103
|
};
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
props.status =
|
|
104
|
+
const close = () => {
|
|
105
|
+
props.status = PROCESS_STATUS.Closing;
|
|
106
|
+
void postMsg(undefined, { type: 'close' });
|
|
107
|
+
props.status = PROCESS_STATUS.Closed;
|
|
108
|
+
};
|
|
109
|
+
const destroy = async () => {
|
|
110
|
+
props.status = PROCESS_STATUS.Destroying;
|
|
111
|
+
await postMsg(undefined, { type: 'destroy' });
|
|
112
|
+
props.mediaWorker?.terminate();
|
|
113
|
+
props.mediaWorker = undefined;
|
|
114
|
+
props.status = PROCESS_STATUS.Destroyed;
|
|
115
|
+
return Promise.resolve();
|
|
30
116
|
};
|
|
31
117
|
return {
|
|
32
|
-
get
|
|
33
|
-
return
|
|
118
|
+
get modelAsset() {
|
|
119
|
+
return props.modelAsset;
|
|
34
120
|
},
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
},
|
|
38
|
-
get height() {
|
|
39
|
-
return processingHeight;
|
|
121
|
+
set modelAsset(asset) {
|
|
122
|
+
props.modelAsset = asset;
|
|
40
123
|
},
|
|
41
124
|
get status() {
|
|
42
125
|
return props.status;
|
|
43
126
|
},
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// Avoid side-effects from the modules
|
|
47
|
-
if (!props.tfjsCoreLoaded) {
|
|
48
|
-
await loadTfjsCore(prodMode);
|
|
49
|
-
props.tfjsCoreLoaded = true;
|
|
50
|
-
}
|
|
51
|
-
if (!props.tfjsBackendLoaded) {
|
|
52
|
-
await loadTfjsBackendWebGl();
|
|
53
|
-
props.tfjsBackendLoaded = true;
|
|
54
|
-
}
|
|
55
|
-
if (!props.glueLoaded) {
|
|
56
|
-
await loadScript(gluePath, gluePath);
|
|
57
|
-
props.glueLoaded = true;
|
|
58
|
-
}
|
|
59
|
-
props.segmenter = new SelfieSegmentation({
|
|
60
|
-
locateFile: (path) => `${basePath.replace(/\/+$/, '')}/${path}`,
|
|
61
|
-
});
|
|
62
|
-
const modelSelection = modelType === 'landscape' ? 1 : 0;
|
|
63
|
-
props.segmenter.setOptions({
|
|
64
|
-
modelSelection,
|
|
65
|
-
selfieMode: props.selfieMode,
|
|
66
|
-
});
|
|
67
|
-
props.segmenter.onResults(results => {
|
|
68
|
-
props.segmentation = [
|
|
69
|
-
toSegmentation(results.segmentationMask, 'canvasimagesource', () => 'person'),
|
|
70
|
-
];
|
|
71
|
-
});
|
|
72
|
-
await props.segmenter.initialize();
|
|
73
|
-
props.status = 'opened';
|
|
74
|
-
},
|
|
75
|
-
process: async (input) => {
|
|
76
|
-
await segmentPerson(input);
|
|
77
|
-
return props.segmentation;
|
|
78
|
-
},
|
|
79
|
-
close: () => {
|
|
80
|
-
props.segmenter?.reset();
|
|
81
|
-
props.status = 'closed';
|
|
127
|
+
get width() {
|
|
128
|
+
return processingWidth;
|
|
82
129
|
},
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
await props.segmenter?.close();
|
|
86
|
-
props.status = 'destroyed';
|
|
130
|
+
get height() {
|
|
131
|
+
return processingHeight;
|
|
87
132
|
},
|
|
133
|
+
open,
|
|
134
|
+
update,
|
|
135
|
+
process,
|
|
136
|
+
close,
|
|
137
|
+
destroy,
|
|
88
138
|
};
|
|
89
139
|
};
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RENDER_EFFECT } from '../../common/constants';
|
|
2
2
|
export const isRenderEffects = (t) => {
|
|
3
3
|
if (typeof t !== 'string') {
|
|
4
4
|
return false;
|
|
5
5
|
}
|
|
6
|
-
return
|
|
7
|
-
};
|
|
8
|
-
export const isSegmentationModel = (t) => {
|
|
9
|
-
if (typeof t !== 'string') {
|
|
10
|
-
return false;
|
|
11
|
-
}
|
|
12
|
-
return SEG_MODELS.some(model => model === t);
|
|
6
|
+
return Object.values(RENDER_EFFECT).some(effect => effect === t);
|
|
13
7
|
};
|