talking-head-studio 0.3.6 → 0.3.7
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/dist/TalkingHead.web.js +7 -17
- package/dist/api/studioApi.js +26 -25
- package/dist/appearance/apply.d.ts +6 -0
- package/dist/appearance/apply.js +72 -7
- package/dist/appearance/index.d.ts +1 -1
- package/dist/appearance/index.js +2 -1
- package/dist/appearance/matchers.js +2 -1
- package/dist/appearance/schema.d.ts +1 -0
- package/dist/appearance/schema.js +3 -1
- package/dist/appearance/sharedState.d.ts +11 -0
- package/dist/appearance/sharedState.js +13 -0
- package/dist/editor/AvatarCanvas.d.ts +1 -0
- package/dist/editor/AvatarCanvas.js +3 -2
- package/dist/editor/AvatarModel.d.ts +1 -3
- package/dist/editor/AvatarModel.js +13 -5
- package/dist/editor/RigidAccessory.js +2 -1
- package/dist/editor/SkinnedClothing.js +9 -18
- package/dist/filament/FilamentAvatar.js +20 -42
- package/dist/filament/editor/FilamentEditor.js +12 -21
- package/dist/filament/editor/FilamentEditor.web.js +2 -1
- package/dist/filament/editor/PrecisionPanel.js +8 -18
- package/dist/filament/editor/boneSnap.js +12 -22
- package/dist/filament/editor/studioTheme.js +2 -2
- package/dist/filament/useAuthedFilamentUri.js +9 -18
- package/dist/html.js +2 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +1 -3
- package/dist/sketchfab/api.js +5 -4
- package/dist/sketchfab/useSketchfabSearch.js +2 -1
- package/dist/tts/useDirectVisemeStream.js +2 -1
- package/dist/tts/useMotionMarkers.d.ts +1 -0
- package/dist/tts/useMotionMarkers.js +2 -1
- package/dist/utils/avatarUtils.js +3 -2
- package/dist/utils/faceLandmarkerToShapeWeights.d.ts +44 -0
- package/dist/utils/faceLandmarkerToShapeWeights.js +112 -0
- package/dist/voice/convertToWav.js +2 -1
- package/dist/voice/useAudioPlayer.js +2 -1
- package/dist/voice/useAudioRecording.js +2 -1
- package/dist/wardrobe/useAvatarWardrobeHydration.js +2 -1
- package/package.json +15 -3
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* faceLandmarkerToShapeWeights
|
|
4
|
+
*
|
|
5
|
+
* Runs MediaPipe FaceLandmarker on a photo and returns a
|
|
6
|
+
* faceShapeWeights Record<string, number> ready to store in
|
|
7
|
+
* AvatarAppearance. Works with any GLB that has ARKit morph
|
|
8
|
+
* target names — mesh-agnostic, pure named floats.
|
|
9
|
+
*
|
|
10
|
+
* Lazy-loads @mediapipe/tasks-vision on first call (~3MB WASM).
|
|
11
|
+
* Subsequent calls reuse the same FaceLandmarker instance.
|
|
12
|
+
*
|
|
13
|
+
* Apache 2.0 — no API key, no license restrictions.
|
|
14
|
+
*/
|
|
15
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
18
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
19
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
20
|
+
}
|
|
21
|
+
Object.defineProperty(o, k2, desc);
|
|
22
|
+
}) : (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
o[k2] = m[k];
|
|
25
|
+
}));
|
|
26
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
27
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
28
|
+
}) : function(o, v) {
|
|
29
|
+
o["default"] = v;
|
|
30
|
+
});
|
|
31
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.prefetchFaceLandmarker = exports.faceLandmarkerToShapeWeights = void 0;
|
|
40
|
+
const MEDIAPIPE_CDN = 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm';
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Singleton — built once, reused across calls
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
let _landmarkerPromise = null;
|
|
45
|
+
async function getLandmarker() {
|
|
46
|
+
if (_landmarkerPromise)
|
|
47
|
+
return _landmarkerPromise;
|
|
48
|
+
_landmarkerPromise = (async () => {
|
|
49
|
+
// Dynamic import so this module is tree-shaken when unused
|
|
50
|
+
// @mediapipe/tasks-vision is loaded at runtime from CDN — not in node_modules.
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
52
|
+
// @ts-ignore
|
|
53
|
+
const vision = await Promise.resolve().then(() => __importStar(require(
|
|
54
|
+
/* webpackChunkName: "mediapipe-tasks-vision" */
|
|
55
|
+
'@mediapipe/tasks-vision')));
|
|
56
|
+
const { FaceLandmarker, FilesetResolver } = vision;
|
|
57
|
+
const filesetResolver = await FilesetResolver.forVisionTasks(MEDIAPIPE_CDN);
|
|
58
|
+
const landmarker = await FaceLandmarker.createFromOptions(filesetResolver, {
|
|
59
|
+
baseOptions: {
|
|
60
|
+
modelAssetPath: 'https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task',
|
|
61
|
+
delegate: 'GPU',
|
|
62
|
+
},
|
|
63
|
+
outputFaceBlendshapes: true,
|
|
64
|
+
runningMode: 'IMAGE',
|
|
65
|
+
numFaces: 1,
|
|
66
|
+
});
|
|
67
|
+
return landmarker;
|
|
68
|
+
})();
|
|
69
|
+
// Reset on failure so next call retries
|
|
70
|
+
_landmarkerPromise.catch(() => {
|
|
71
|
+
_landmarkerPromise = null;
|
|
72
|
+
});
|
|
73
|
+
return _landmarkerPromise;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Detect face blendshapes from a photo and return a faceShapeWeights
|
|
77
|
+
* dict compatible with AvatarAppearance.
|
|
78
|
+
*
|
|
79
|
+
* @param source Any image/video/canvas/bitmap element
|
|
80
|
+
* @param options Optional thresholds
|
|
81
|
+
* @returns faceShapeWeights Record<string, number>, or null if no face detected
|
|
82
|
+
*/
|
|
83
|
+
async function faceLandmarkerToShapeWeights(source, options = {}) {
|
|
84
|
+
const { minScore = 0.01, includeNeutral = false } = options;
|
|
85
|
+
const landmarker = await getLandmarker();
|
|
86
|
+
const result = landmarker.detect(source);
|
|
87
|
+
const blendshapes = result.faceBlendshapes?.[0]?.categories;
|
|
88
|
+
if (!blendshapes || blendshapes.length === 0) {
|
|
89
|
+
return null; // No face detected
|
|
90
|
+
}
|
|
91
|
+
const weights = {};
|
|
92
|
+
for (const { categoryName, score } of blendshapes) {
|
|
93
|
+
// MediaPipe includes '_neutral' as a catch-all — skip unless requested
|
|
94
|
+
if (!includeNeutral && categoryName === '_neutral')
|
|
95
|
+
continue;
|
|
96
|
+
if (score < minScore)
|
|
97
|
+
continue;
|
|
98
|
+
weights[categoryName] = Math.round(score * 1000) / 1000; // 3 dp is plenty
|
|
99
|
+
}
|
|
100
|
+
return weights;
|
|
101
|
+
}
|
|
102
|
+
exports.faceLandmarkerToShapeWeights = faceLandmarkerToShapeWeights;
|
|
103
|
+
/**
|
|
104
|
+
* Load the FaceLandmarker WASM bundle eagerly — call this on app start
|
|
105
|
+
* to avoid a cold-start delay when the user first uploads a photo.
|
|
106
|
+
*/
|
|
107
|
+
function prefetchFaceLandmarker() {
|
|
108
|
+
getLandmarker().catch(() => {
|
|
109
|
+
// Silently ignore prefetch failures — will retry on actual use
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
exports.prefetchFaceLandmarker = prefetchFaceLandmarker;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.convertToWav =
|
|
3
|
+
exports.convertToWav = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Convert any audio blob to WAV format using the Web Audio API.
|
|
6
6
|
* Handles WebM/opus output from MediaRecorder without requiring ffmpeg.
|
|
@@ -19,6 +19,7 @@ async function convertToWav(audioBlob) {
|
|
|
19
19
|
await audioContext.close();
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
exports.convertToWav = convertToWav;
|
|
22
23
|
/**
|
|
23
24
|
* Encode an AudioBuffer as a 16-bit PCM WAV blob.
|
|
24
25
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.useAudioPlayer =
|
|
3
|
+
exports.useAudioPlayer = void 0;
|
|
4
4
|
const react_1 = require("react");
|
|
5
5
|
function useAudioPlayer({ onError, } = {}) {
|
|
6
6
|
const [isPlaying, setIsPlaying] = (0, react_1.useState)(false);
|
|
@@ -62,3 +62,4 @@ function useAudioPlayer({ onError, } = {}) {
|
|
|
62
62
|
cleanup,
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
|
+
exports.useAudioPlayer = useAudioPlayer;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.useAudioRecording =
|
|
3
|
+
exports.useAudioRecording = void 0;
|
|
4
4
|
const react_1 = require("react");
|
|
5
5
|
const convertToWav_1 = require("./convertToWav");
|
|
6
6
|
function useAudioRecording({ maxDurationSeconds = 29, onRecordingComplete, } = {}) {
|
|
@@ -163,3 +163,4 @@ function useAudioRecording({ maxDurationSeconds = 29, onRecordingComplete, } = {
|
|
|
163
163
|
cancelRecording,
|
|
164
164
|
};
|
|
165
165
|
}
|
|
166
|
+
exports.useAudioRecording = useAudioRecording;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.useAvatarWardrobeHydration =
|
|
3
|
+
exports.useAvatarWardrobeHydration = void 0;
|
|
4
4
|
const react_1 = require("react");
|
|
5
5
|
const studioApi_1 = require("../api/studioApi");
|
|
6
6
|
const wardrobeStore_1 = require("./wardrobeStore");
|
|
@@ -32,3 +32,4 @@ function useAvatarWardrobeHydration({ avatarId, accessories, }) {
|
|
|
32
32
|
};
|
|
33
33
|
}, [accessorySignature, avatarId, hydrateFromApi]);
|
|
34
34
|
}
|
|
35
|
+
exports.useAvatarWardrobeHydration = useAvatarWardrobeHydration;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "talking-head-studio",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "Cross-platform 3D avatar component for React Native & web — lip-sync, gestures, accessories, and LLM integration. Powered by TalkingHead + Three.js.",
|
|
5
5
|
"main": "dist/index.web.js",
|
|
6
6
|
"browser": "dist/index.web.js",
|
|
@@ -58,7 +58,8 @@
|
|
|
58
58
|
"lint": "eslint 'src/**/*.{ts,tsx}'",
|
|
59
59
|
"format": "prettier --write 'src/**/*.{ts,tsx}'",
|
|
60
60
|
"test": "jest",
|
|
61
|
-
"prepublishOnly": "npm run lint && npm run typecheck && npm test -- --runInBand && npm run build"
|
|
61
|
+
"prepublishOnly": "npm run lint && npm run typecheck && npm test -- --runInBand && npm run build",
|
|
62
|
+
"build:creator": "vite build --config creator-app/vite.config.ts"
|
|
62
63
|
},
|
|
63
64
|
"keywords": [
|
|
64
65
|
"react-native",
|
|
@@ -97,6 +98,7 @@
|
|
|
97
98
|
},
|
|
98
99
|
"sideEffects": false,
|
|
99
100
|
"dependencies": {
|
|
101
|
+
"@mediapipe/tasks-vision": "^0.10.34",
|
|
100
102
|
"zustand": "^5.0.12"
|
|
101
103
|
},
|
|
102
104
|
"peerDependencies": {
|
|
@@ -146,12 +148,17 @@
|
|
|
146
148
|
"@babel/preset-typescript": "^7.28.5",
|
|
147
149
|
"@expo/vector-icons": "^15.1.1",
|
|
148
150
|
"@react-native/babel-preset": "^0.84.1",
|
|
151
|
+
"@react-three/drei": "^9.122.0",
|
|
152
|
+
"@react-three/fiber": "^8.18.0",
|
|
149
153
|
"@testing-library/react-native": "^13.3.3",
|
|
150
154
|
"@types/jest": "^30.0.0",
|
|
151
155
|
"@types/react": "^19.2.14",
|
|
156
|
+
"@types/react-dom": "^18.3.7",
|
|
152
157
|
"@types/react-native": "^0.73.0",
|
|
158
|
+
"@types/three": "^0.180.0",
|
|
153
159
|
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
|
154
160
|
"@typescript-eslint/parser": "^8.56.1",
|
|
161
|
+
"@vitejs/plugin-react": "^4.7.0",
|
|
155
162
|
"babel-jest": "^30.2.0",
|
|
156
163
|
"eslint": "^9.39.3",
|
|
157
164
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -166,10 +173,15 @@
|
|
|
166
173
|
"moti": "^0.30.0",
|
|
167
174
|
"multer": "^2.1.0",
|
|
168
175
|
"prettier": "^3.8.1",
|
|
176
|
+
"react": "^18.3.1",
|
|
177
|
+
"react-dom": "^18.3.1",
|
|
169
178
|
"react-native-gesture-handler": "^2.30.0",
|
|
170
179
|
"react-native-reanimated": "^4.2.3",
|
|
171
180
|
"react-native-webview": "^13.16.0",
|
|
172
181
|
"react-test-renderer": "^19.2.4",
|
|
173
|
-
"
|
|
182
|
+
"three": "^0.180.0",
|
|
183
|
+
"ts-jest": "^29.4.6",
|
|
184
|
+
"typescript": "^5.3.3",
|
|
185
|
+
"vite": "^5.4.21"
|
|
174
186
|
}
|
|
175
187
|
}
|