@pexip/media-processor 17.4.0 → 18.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.
Files changed (83) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/common/backends/webgl/blender.d.ts +11 -0
  3. package/dist/common/backends/webgl/blender.js +104 -0
  4. package/dist/common/backends/webgl/blur.d.ts +5 -0
  5. package/dist/common/backends/webgl/blur.js +153 -0
  6. package/dist/common/backends/webgl/canvas.d.ts +5 -0
  7. package/dist/common/backends/webgl/canvas.js +77 -0
  8. package/dist/common/backends/webgl/renderer.d.ts +8 -0
  9. package/dist/common/backends/webgl/renderer.js +126 -0
  10. package/dist/common/backends/webgl/shaders/blendFragmentShader.d.ts +1 -0
  11. package/dist/common/backends/webgl/shaders/blendFragmentShader.js +63 -0
  12. package/dist/common/backends/webgl/shaders/dualFilterBlurFragmentShader.d.ts +1 -0
  13. package/dist/common/backends/webgl/shaders/dualFilterBlurFragmentShader.js +45 -0
  14. package/dist/common/backends/webgl/shaders/textureFragmentShader.d.ts +1 -0
  15. package/dist/common/backends/webgl/shaders/textureFragmentShader.js +16 -0
  16. package/dist/common/backends/webgl/shaders/vertexShader.d.ts +1 -0
  17. package/dist/common/backends/webgl/shaders/vertexShader.js +19 -0
  18. package/dist/common/backends/webgl/texture.d.ts +6 -0
  19. package/dist/common/backends/webgl/texture.js +94 -0
  20. package/dist/common/backends/webgl/types.d.ts +5 -0
  21. package/dist/common/backends/webgl/types.js +1 -0
  22. package/dist/common/backends/webgl/webgl.mock.js +8 -0
  23. package/dist/common/backends/webgl/webglUtils.d.ts +80 -0
  24. package/dist/common/backends/webgl/webglUtils.js +194 -0
  25. package/dist/common/canvasRenderUtils.d.ts +62 -0
  26. package/dist/{main/video → common}/canvasRenderUtils.js +100 -61
  27. package/dist/common/constants.d.ts +27 -0
  28. package/dist/common/constants.js +23 -0
  29. package/dist/common/index.d.ts +9 -0
  30. package/dist/common/index.js +9 -0
  31. package/dist/common/m3.d.ts +17 -0
  32. package/dist/common/m3.js +69 -0
  33. package/dist/common/tsconfig.tsbuildinfo +1 -0
  34. package/dist/common/types/media.d.ts +22 -0
  35. package/dist/common/types/media.js +1 -0
  36. package/dist/common/types/messageEvents.d.ts +31 -0
  37. package/dist/common/types/messageEvents.js +1 -0
  38. package/dist/common/types/processor.d.ts +15 -0
  39. package/dist/common/types/processor.js +1 -0
  40. package/dist/common/types/render.d.ts +38 -0
  41. package/dist/common/types/render.js +1 -0
  42. package/dist/common/types/segmentation.d.ts +99 -0
  43. package/dist/common/types/segmentation.js +4 -0
  44. package/dist/common/types/utils.d.ts +19 -0
  45. package/dist/common/types/utils.js +1 -0
  46. package/dist/common/utils.d.ts +35 -0
  47. package/dist/common/utils.js +129 -0
  48. package/dist/main/benchUtils.d.ts +3 -2
  49. package/dist/main/benchUtils.js +7 -6
  50. package/dist/main/index.d.ts +2 -0
  51. package/dist/main/index.js +2 -0
  52. package/dist/main/process.d.ts +1 -11
  53. package/dist/main/process.js +0 -20
  54. package/dist/main/tsconfig.tsbuildinfo +1 -1
  55. package/dist/main/types.d.ts +2 -2
  56. package/dist/main/video/canvasTransform.d.ts +14 -5
  57. package/dist/main/video/canvasTransform.js +101 -86
  58. package/dist/main/video/constants.d.ts +0 -6
  59. package/dist/main/video/constants.js +0 -6
  60. package/dist/main/video/index.d.ts +2 -2
  61. package/dist/main/video/index.js +2 -2
  62. package/dist/main/video/load.d.ts +0 -14
  63. package/dist/main/video/load.js +0 -8
  64. package/dist/main/video/segmenters/mediapipe.d.ts +9 -11
  65. package/dist/main/video/segmenters/mediapipe.js +127 -77
  66. package/dist/main/video/typeGuards.d.ts +0 -1
  67. package/dist/main/video/typeGuards.js +2 -8
  68. package/dist/main/video/types.d.ts +19 -61
  69. package/dist/main/video/types.js +1 -20
  70. package/dist/main/video/utils.d.ts +5 -12
  71. package/dist/main/video/utils.js +5 -152
  72. package/dist/main/video/video.js +1 -0
  73. package/dist/main/video/videoStreamTrackProcessor.js +7 -4
  74. package/dist/workers/global.d.js +0 -0
  75. package/dist/workers/mediaWorker.d.ts +1 -0
  76. package/dist/workers/mediaWorker.js +188 -0
  77. package/dist/workers/tsconfig.tsbuildinfo +1 -1
  78. package/package.json +21 -13
  79. package/dist/main/video/canvasRenderUtils.d.ts +0 -27
  80. package/dist/main/video/segmenters/index.d.ts +0 -1
  81. package/dist/main/video/segmenters/index.js +0 -1
  82. package/dist/workers/index.js +0 -1
  83. /package/dist/{workers/index.d.ts → common/backends/webgl/webgl.mock.d.ts} +0 -0
@@ -1,16 +1,9 @@
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, } = {}) => {
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
- backgroundImage: undefined,
25
- status: 'created',
26
- backgroundImageUrl: bgImageUrl,
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
- if (props.segmenter.status === 'created' ||
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
- props.backgroundImageUrl = url;
43
- props.backgroundImage = await props.utils.loadBackgroundImage(url);
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 backgroundImage() {
71
- return props.backgroundImage;
87
+ get maskCombineRatio() {
88
+ return props.maskCombineRatio;
72
89
  },
73
90
  set foregroundThreshold(value) {
74
- assertInRangeFrom0To1(value);
75
- props.foregroundThreshold = value;
91
+ props.foregroundThreshold = clampInRangeFrom0To1(value);
76
92
  },
77
93
  set backgroundBlurAmount(value) {
78
- assertInRangeFrom0To20(value);
79
- props.backgroundBlurAmount = value;
94
+ props.backgroundBlurAmount = clampInRangeFrom0To9(value);
80
95
  },
81
96
  set edgeBlurAmount(value) {
82
- assertInRangeFrom0To20(value);
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 backgroundImage(canvas) {
95
- props.backgroundImage = canvas;
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: async () => {
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 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') {
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
- 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);
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';
@@ -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
- }>;
@@ -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, 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;
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?: string, { modelType, tfjsCoreLoaded, tfjsBackendLoaded, glueLoaded, processingWidth, processingHeight, gluePath, selfieMode, prodMode, }?: Partial<Options>) => Segmenter;
10
+ export declare const createSegmenter: (basePath: SegmenterOptions['basePath'], { processingWidth, processingHeight, workerScriptUrl, credentials, ...options }: Options) => Segmenter;
13
11
  export {};
@@ -1,89 +1,139 @@
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,
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 toInputImage = createInputImageConvertor(input => props.renderUtils.drawAndBlurImageOnOffScreenCanvas({
20
- image: input,
21
- blurAmount: 0,
22
- offscreenCanvasName: 'blurredCanvas',
23
- }));
24
- const segmentPerson = async (input) => {
25
- const segmenter = ensure(props.segmenter);
26
- props.status = 'processing';
27
- const image = await toInputImage(input);
28
- await segmenter.send({ image });
29
- props.status = 'idle';
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 model() {
33
- return 'mediapipeSelfie';
118
+ get modelAsset() {
119
+ return props.modelAsset;
34
120
  },
35
- get width() {
36
- return processingWidth;
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
- open: async () => {
45
- props.status = 'opening';
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
- destroy: async () => {
84
- props.status = 'destroying';
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,2 +1 @@
1
1
  export declare const isRenderEffects: (t: unknown) => t is "blur" | "none" | "overlay";
2
- export declare const isSegmentationModel: (t: unknown) => t is "mediapipeSelfie";
@@ -1,13 +1,7 @@
1
- import { RENDER_EFFECTS, SEG_MODELS } from './types';
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 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);
6
+ return Object.values(RENDER_EFFECT).some(effect => effect === t);
13
7
  };