talking-head-studio 0.2.8 → 0.3.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/dist/TalkingHead.d.ts +8 -0
- package/dist/TalkingHead.js +104 -7
- package/dist/TalkingHead.web.d.ts +3 -2
- package/dist/TalkingHead.web.js +17 -2
- package/dist/TalkingHeadVisualization.d.ts +35 -0
- package/dist/TalkingHeadVisualization.js +277 -0
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +18 -0
- package/dist/api/studioApi.d.ts +38 -0
- package/dist/api/studioApi.js +235 -0
- package/dist/api/types.d.ts +87 -0
- package/dist/api/types.js +5 -0
- package/dist/assets/face-squeeze-local.glb +0 -0
- package/dist/filament/FilamentAvatar.d.ts +41 -0
- package/dist/filament/FilamentAvatar.js +737 -0
- package/dist/filament/faceSqueezeAssets.d.ts +1 -0
- package/dist/filament/faceSqueezeAssets.js +5 -0
- package/dist/filament/index.d.ts +5 -0
- package/dist/filament/index.js +22 -0
- package/dist/filament/morphTables.d.ts +5 -0
- package/dist/filament/morphTables.js +93 -0
- package/dist/filament/useAuthedFilamentUri.d.ts +11 -0
- package/dist/filament/useAuthedFilamentUri.js +126 -0
- package/dist/html.d.ts +7 -0
- package/dist/html.js +255 -56
- package/dist/index.d.ts +9 -2
- package/dist/index.js +13 -2
- package/dist/index.web.d.ts +6 -2
- package/dist/index.web.js +10 -2
- package/dist/utils/avatarUtils.d.ts +13 -0
- package/dist/utils/avatarUtils.js +56 -0
- package/dist/wardrobe/index.d.ts +2 -0
- package/dist/wardrobe/index.js +20 -0
- package/dist/wardrobe/useAvatarWardrobeHydration.d.ts +7 -0
- package/dist/wardrobe/useAvatarWardrobeHydration.js +34 -0
- package/dist/wardrobe/wardrobeStore.d.ts +30 -0
- package/dist/wardrobe/wardrobeStore.js +106 -0
- package/package.json +33 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const FACE_SQUEEZE_LOCAL_MODULE: number;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FACE_SQUEEZE_LOCAL_MODULE = void 0;
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
5
|
+
exports.FACE_SQUEEZE_LOCAL_MODULE = require('../assets/face-squeeze-local.glb');
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { FilamentAvatar } from './FilamentAvatar';
|
|
2
|
+
export type { FilamentAvatarRef } from './FilamentAvatar';
|
|
3
|
+
export { useAuthedFilamentUri } from './useAuthedFilamentUri';
|
|
4
|
+
export type { AuthedFileResult } from './useAuthedFilamentUri';
|
|
5
|
+
export * from './morphTables';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.useAuthedFilamentUri = exports.FilamentAvatar = void 0;
|
|
18
|
+
var FilamentAvatar_1 = require("./FilamentAvatar");
|
|
19
|
+
Object.defineProperty(exports, "FilamentAvatar", { enumerable: true, get: function () { return FilamentAvatar_1.FilamentAvatar; } });
|
|
20
|
+
var useAuthedFilamentUri_1 = require("./useAuthedFilamentUri");
|
|
21
|
+
Object.defineProperty(exports, "useAuthedFilamentUri", { enumerable: true, get: function () { return useAuthedFilamentUri_1.useAuthedFilamentUri; } });
|
|
22
|
+
__exportStar(require("./morphTables"), exports);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const RHUBARB_TO_VISEME: Record<string, string>;
|
|
2
|
+
export declare const VISEME_MORPH_ALIASES: Record<string, string[]>;
|
|
3
|
+
export declare const VISEME_WEIGHTS: Record<string, number>;
|
|
4
|
+
export declare const DEFAULT_VISEME_WEIGHT = 0.35;
|
|
5
|
+
export declare const MOOD_MORPHS: Record<string, Record<string, number>>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Morph target tables for Filament avatar viseme/mood rendering
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MOOD_MORPHS = exports.DEFAULT_VISEME_WEIGHT = exports.VISEME_WEIGHTS = exports.VISEME_MORPH_ALIASES = exports.RHUBARB_TO_VISEME = void 0;
|
|
7
|
+
// Rhubarb → viseme key mapping (mirrors html.ts)
|
|
8
|
+
exports.RHUBARB_TO_VISEME = {
|
|
9
|
+
A: 'aa',
|
|
10
|
+
B: 'PP',
|
|
11
|
+
C: 'ih',
|
|
12
|
+
D: 'FF',
|
|
13
|
+
E: 'ee',
|
|
14
|
+
F: 'oh',
|
|
15
|
+
G: 'ou',
|
|
16
|
+
H: 'nn',
|
|
17
|
+
X: 'sil',
|
|
18
|
+
};
|
|
19
|
+
// Oculus viseme morph name aliases per viseme key
|
|
20
|
+
exports.VISEME_MORPH_ALIASES = {
|
|
21
|
+
sil: ['viseme_sil', 'sil', 'mouthClose'],
|
|
22
|
+
PP: ['viseme_PP', 'viseme_pp', 'mouthPucker'],
|
|
23
|
+
FF: ['viseme_FF', 'viseme_ff', 'mouthRollLower', 'mouthShrugLower'],
|
|
24
|
+
TH: ['viseme_TH', 'viseme_th', 'tongueOut'],
|
|
25
|
+
DD: ['viseme_DD', 'viseme_dd', 'mouthShrugUpper'],
|
|
26
|
+
kk: ['viseme_kk', 'viseme_k', 'mouthStretchLeft'],
|
|
27
|
+
CH: ['viseme_CH', 'viseme_ch', 'mouthSmileLeft', 'mouthSmile'],
|
|
28
|
+
SS: ['viseme_SS', 'viseme_ss', 'mouthStretchRight'],
|
|
29
|
+
nn: ['viseme_nn', 'viseme_n', 'mouthDimpleLeft'],
|
|
30
|
+
RR: ['viseme_RR', 'viseme_r', 'mouthDimpleRight'],
|
|
31
|
+
aa: ['viseme_aa', 'viseme_AA', 'jawOpen', 'mouthOpen'],
|
|
32
|
+
ee: ['viseme_E', 'viseme_ee', 'mouthSmileLeft'],
|
|
33
|
+
ih: ['viseme_I', 'viseme_ih', 'mouthSmileRight'],
|
|
34
|
+
oh: ['viseme_O', 'viseme_oh', 'mouthFunnel'],
|
|
35
|
+
ou: ['viseme_U', 'viseme_ou', 'mouthRollLower'],
|
|
36
|
+
};
|
|
37
|
+
exports.VISEME_WEIGHTS = {
|
|
38
|
+
PP: 0.45, FF: 0.40, ee: 0.38, ih: 0.35,
|
|
39
|
+
oh: 0.35, ou: 0.32, aa: 0.40,
|
|
40
|
+
};
|
|
41
|
+
exports.DEFAULT_VISEME_WEIGHT = 0.35;
|
|
42
|
+
// Mood → baseline morph weights (persistent low-level expression layer)
|
|
43
|
+
exports.MOOD_MORPHS = {
|
|
44
|
+
neutral: {},
|
|
45
|
+
happy: {
|
|
46
|
+
mouthSmileLeft: 0.35, mouthSmileRight: 0.35,
|
|
47
|
+
cheekSquintLeft: 0.2, cheekSquintRight: 0.2,
|
|
48
|
+
},
|
|
49
|
+
sad: {
|
|
50
|
+
browInnerUp: 0.5, mouthFrownLeft: 0.4, mouthFrownRight: 0.4,
|
|
51
|
+
eyeLookDownLeft: 0.2, eyeLookDownRight: 0.2,
|
|
52
|
+
},
|
|
53
|
+
angry: {
|
|
54
|
+
browDownLeft: 0.5, browDownRight: 0.5,
|
|
55
|
+
noseSneerLeft: 0.3, noseSneerRight: 0.3,
|
|
56
|
+
eyeSquintLeft: 0.2, eyeSquintRight: 0.2,
|
|
57
|
+
},
|
|
58
|
+
surprised: {
|
|
59
|
+
eyeWideLeft: 0.5, eyeWideRight: 0.5,
|
|
60
|
+
browInnerUp: 0.5, browOuterUpLeft: 0.4, browOuterUpRight: 0.4,
|
|
61
|
+
jawOpen: 0.1,
|
|
62
|
+
},
|
|
63
|
+
excited: {
|
|
64
|
+
mouthSmileLeft: 0.55, mouthSmileRight: 0.55,
|
|
65
|
+
eyeWideLeft: 0.3, eyeWideRight: 0.3,
|
|
66
|
+
cheekSquintLeft: 0.3, cheekSquintRight: 0.3,
|
|
67
|
+
browOuterUpLeft: 0.3, browOuterUpRight: 0.3,
|
|
68
|
+
},
|
|
69
|
+
thinking: {
|
|
70
|
+
browInnerUp: 0.3,
|
|
71
|
+
eyeLookUpLeft: 0.2, eyeLookUpRight: 0.2,
|
|
72
|
+
mouthPucker: 0.15,
|
|
73
|
+
},
|
|
74
|
+
concerned: {
|
|
75
|
+
browInnerUp: 0.45, browDownLeft: 0.25, browDownRight: 0.25,
|
|
76
|
+
mouthFrownLeft: 0.2, mouthFrownRight: 0.2,
|
|
77
|
+
},
|
|
78
|
+
disgust: {
|
|
79
|
+
noseSneerLeft: 0.6, noseSneerRight: 0.6,
|
|
80
|
+
browDownLeft: 0.4, browDownRight: 0.4,
|
|
81
|
+
mouthShrugUpper: 0.3,
|
|
82
|
+
},
|
|
83
|
+
fear: {
|
|
84
|
+
eyeWideLeft: 0.6, eyeWideRight: 0.6,
|
|
85
|
+
browInnerUp: 0.7, browOuterUpLeft: 0.3, browOuterUpRight: 0.3,
|
|
86
|
+
mouthStretchLeft: 0.2, mouthStretchRight: 0.2,
|
|
87
|
+
},
|
|
88
|
+
exhausted: {
|
|
89
|
+
eyeBlinkLeft: 0.4, eyeBlinkRight: 0.4,
|
|
90
|
+
browDownLeft: 0.3, browDownRight: 0.3,
|
|
91
|
+
mouthFrownLeft: 0.2, mouthFrownRight: 0.2,
|
|
92
|
+
},
|
|
93
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type AuthedFileResult = {
|
|
2
|
+
uri: string;
|
|
3
|
+
size: number;
|
|
4
|
+
} | null;
|
|
5
|
+
/**
|
|
6
|
+
* Downloads a remote URL (with Bearer auth) to the local cache and returns a
|
|
7
|
+
* `file://` URI suitable for react-native-filament's <Model> source prop.
|
|
8
|
+
* The native Filament fetcher doesn't send auth headers, so we pre-fetch here.
|
|
9
|
+
* Also returns the file size in bytes for GPU memory budgeting.
|
|
10
|
+
*/
|
|
11
|
+
export declare function useAuthedFilamentUri(remoteUrl: string | null): AuthedFileResult;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.useAuthedFilamentUri = useAuthedFilamentUri;
|
|
37
|
+
const react_1 = require("react");
|
|
38
|
+
const FileSystem = __importStar(require("expo-file-system/legacy"));
|
|
39
|
+
const studioApi_1 = require("../api/studioApi");
|
|
40
|
+
// Module-level set of URLs that returned non-GLB responses — skip retrying them.
|
|
41
|
+
const failedUrls = new Set();
|
|
42
|
+
/**
|
|
43
|
+
* Downloads a remote URL (with Bearer auth) to the local cache and returns a
|
|
44
|
+
* `file://` URI suitable for react-native-filament's <Model> source prop.
|
|
45
|
+
* The native Filament fetcher doesn't send auth headers, so we pre-fetch here.
|
|
46
|
+
* Also returns the file size in bytes for GPU memory budgeting.
|
|
47
|
+
*/
|
|
48
|
+
function useAuthedFilamentUri(remoteUrl) {
|
|
49
|
+
const [result, setResult] = (0, react_1.useState)(null);
|
|
50
|
+
(0, react_1.useEffect)(() => {
|
|
51
|
+
if (!remoteUrl) {
|
|
52
|
+
setResult(null);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// Skip URLs that previously returned a non-GLB response (e.g. 404)
|
|
56
|
+
if (failedUrls.has(remoteUrl))
|
|
57
|
+
return;
|
|
58
|
+
// Pass through non-HTTP URLs (file://, data:, asset://) without downloading
|
|
59
|
+
if (!remoteUrl.startsWith('http://') && !remoteUrl.startsWith('https://')) {
|
|
60
|
+
setResult({ uri: remoteUrl, size: 0 });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
let cancelled = false;
|
|
64
|
+
(async () => {
|
|
65
|
+
try {
|
|
66
|
+
const token = await (0, studioApi_1.getToken)();
|
|
67
|
+
// Strip query params (access_token changes per session) so the same GLB is cached across sessions
|
|
68
|
+
const urlWithoutQuery = remoteUrl.split('?')[0];
|
|
69
|
+
const key = urlWithoutQuery.replace(/[^a-zA-Z0-9]/g, '_').slice(-100);
|
|
70
|
+
const dir = `${FileSystem.cacheDirectory}filament/`;
|
|
71
|
+
// Append .glb so native loaders can identify the format
|
|
72
|
+
const localPath = `${dir}${key}.glb`;
|
|
73
|
+
await FileSystem.makeDirectoryAsync(dir, { intermediates: true });
|
|
74
|
+
const info = await FileSystem.getInfoAsync(localPath);
|
|
75
|
+
if (info.exists && info.size && info.size > 1000) {
|
|
76
|
+
// Verify magic bytes — GLB files start with 0x676C5446 ("glTF")
|
|
77
|
+
const header = await FileSystem.readAsStringAsync(localPath, {
|
|
78
|
+
encoding: FileSystem.EncodingType.Base64,
|
|
79
|
+
length: 4,
|
|
80
|
+
position: 0,
|
|
81
|
+
});
|
|
82
|
+
// base64("glTF") == "Z2xURg=="
|
|
83
|
+
if (header === 'Z2xURg==') {
|
|
84
|
+
if (!cancelled)
|
|
85
|
+
setResult({ uri: localPath, size: info.size });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// Cached file isn't a valid GLB — delete and re-download
|
|
89
|
+
await FileSystem.deleteAsync(localPath);
|
|
90
|
+
}
|
|
91
|
+
const dlResult = await FileSystem.downloadAsync(remoteUrl, localPath, {
|
|
92
|
+
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
93
|
+
});
|
|
94
|
+
// Verify we got a valid GLB — check size and magic bytes ("glTF")
|
|
95
|
+
const downloaded = await FileSystem.getInfoAsync(dlResult.uri);
|
|
96
|
+
const downloadedSize = downloaded.size;
|
|
97
|
+
if (!downloaded.exists || !downloadedSize || downloadedSize < 1000) {
|
|
98
|
+
console.error('[useAuthedFilamentUri] Download too small (' + (downloadedSize ?? 0) + 'B), likely an error response');
|
|
99
|
+
failedUrls.add(remoteUrl);
|
|
100
|
+
await FileSystem.deleteAsync(dlResult.uri);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const dlHeader = await FileSystem.readAsStringAsync(dlResult.uri, {
|
|
104
|
+
encoding: FileSystem.EncodingType.Base64,
|
|
105
|
+
length: 4,
|
|
106
|
+
position: 0,
|
|
107
|
+
});
|
|
108
|
+
if (dlHeader !== 'Z2xURg==') {
|
|
109
|
+
console.error('[useAuthedFilamentUri] Downloaded file is not a valid GLB (bad magic bytes)');
|
|
110
|
+
failedUrls.add(remoteUrl);
|
|
111
|
+
await FileSystem.deleteAsync(dlResult.uri);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (!cancelled)
|
|
115
|
+
setResult({ uri: dlResult.uri, size: downloadedSize });
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
console.error('[useAuthedFilamentUri] Failed to download:', remoteUrl.slice(-60), e);
|
|
119
|
+
}
|
|
120
|
+
})();
|
|
121
|
+
return () => {
|
|
122
|
+
cancelled = true;
|
|
123
|
+
};
|
|
124
|
+
}, [remoteUrl]);
|
|
125
|
+
return result;
|
|
126
|
+
}
|
package/dist/html.d.ts
CHANGED
|
@@ -8,5 +8,12 @@ export type AvatarConfig = {
|
|
|
8
8
|
initialHairColor?: string;
|
|
9
9
|
initialSkinColor?: string;
|
|
10
10
|
initialEyeColor?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Base URL for vendored static assets (three.js, talkinghead.mjs, headaudio).
|
|
13
|
+
* When set, replaces all cdn.jsdelivr.net references so the WebView loads
|
|
14
|
+
* assets from your own server instead of an external CDN.
|
|
15
|
+
* Example: "https://studio.sitebay.org/vendor"
|
|
16
|
+
*/
|
|
17
|
+
vendorBaseUrl?: string | null;
|
|
11
18
|
};
|
|
12
19
|
export declare function buildAvatarHtml(config: AvatarConfig): string;
|