talking-head-studio 0.3.6 → 0.3.8

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 (40) hide show
  1. package/dist/TalkingHead.web.js +7 -17
  2. package/dist/api/studioApi.js +26 -25
  3. package/dist/appearance/apply.d.ts +6 -0
  4. package/dist/appearance/apply.js +72 -7
  5. package/dist/appearance/index.d.ts +1 -1
  6. package/dist/appearance/index.js +2 -1
  7. package/dist/appearance/matchers.js +2 -1
  8. package/dist/appearance/schema.d.ts +1 -0
  9. package/dist/appearance/schema.js +3 -1
  10. package/dist/appearance/sharedState.d.ts +11 -0
  11. package/dist/appearance/sharedState.js +13 -0
  12. package/dist/editor/AvatarCanvas.d.ts +1 -0
  13. package/dist/editor/AvatarCanvas.js +3 -2
  14. package/dist/editor/AvatarModel.d.ts +1 -3
  15. package/dist/editor/AvatarModel.js +13 -5
  16. package/dist/editor/RigidAccessory.js +2 -1
  17. package/dist/editor/SkinnedClothing.js +9 -18
  18. package/dist/filament/FilamentAvatar.js +20 -42
  19. package/dist/filament/editor/FilamentEditor.js +12 -21
  20. package/dist/filament/editor/FilamentEditor.web.js +2 -1
  21. package/dist/filament/editor/PrecisionPanel.js +8 -18
  22. package/dist/filament/editor/boneSnap.js +13 -23
  23. package/dist/filament/editor/studioTheme.js +2 -2
  24. package/dist/filament/useAuthedFilamentUri.js +9 -18
  25. package/dist/html.js +2 -1
  26. package/dist/index.d.ts +0 -2
  27. package/dist/index.js +1 -3
  28. package/dist/sketchfab/api.js +5 -4
  29. package/dist/sketchfab/useSketchfabSearch.js +2 -1
  30. package/dist/tts/useDirectVisemeStream.js +2 -1
  31. package/dist/tts/useMotionMarkers.d.ts +1 -0
  32. package/dist/tts/useMotionMarkers.js +2 -1
  33. package/dist/utils/avatarUtils.js +3 -2
  34. package/dist/utils/faceLandmarkerToShapeWeights.d.ts +44 -0
  35. package/dist/utils/faceLandmarkerToShapeWeights.js +112 -0
  36. package/dist/voice/convertToWav.js +2 -1
  37. package/dist/voice/useAudioPlayer.js +2 -1
  38. package/dist/voice/useAudioRecording.js +2 -1
  39. package/dist/wardrobe/useAvatarWardrobeHydration.js +2 -1
  40. 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 = 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 = 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 = 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 = 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.6",
3
+ "version": "0.3.8",
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
- "ts-jest": "^29.4.6"
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
  }