@sage-rsc/talking-head-react 1.3.3 ā 1.3.4
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/index.cjs +2 -2
- package/dist/index.js +318 -311
- package/package.json +1 -1
- package/scripts/generate-animation-manifest.js +90 -17
- package/src/components/SimpleTalkingAvatar.jsx +27 -3
package/package.json
CHANGED
|
@@ -20,6 +20,7 @@ const outputFile = path.join(animationsDir, 'manifest.json');
|
|
|
20
20
|
|
|
21
21
|
function scanDirectory(dir) {
|
|
22
22
|
const groups = {};
|
|
23
|
+
const genderGroups = { male: {}, female: {}, shared: {} };
|
|
23
24
|
|
|
24
25
|
if (!fs.existsSync(dir)) {
|
|
25
26
|
console.error(`Directory does not exist: ${dir}`);
|
|
@@ -28,29 +29,101 @@ function scanDirectory(dir) {
|
|
|
28
29
|
|
|
29
30
|
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
// First pass: Check for gender-specific top-level directories
|
|
33
|
+
const hasGenderDirs = items.some(item => {
|
|
34
|
+
if (!item.isDirectory()) return false;
|
|
35
|
+
const name = item.name.toLowerCase();
|
|
36
|
+
return name === 'male' || name === 'female' || name === 'm' || name === 'f' ||
|
|
37
|
+
name.startsWith('male_') || name.startsWith('female_') ||
|
|
38
|
+
name.startsWith('m_') || name.startsWith('f_');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (hasGenderDirs) {
|
|
42
|
+
// Structure: animations/male/talking/, animations/female/talking/
|
|
43
|
+
for (const item of items) {
|
|
44
|
+
if (!item.isDirectory()) continue;
|
|
45
|
+
|
|
46
|
+
const dirNameLower = item.name.toLowerCase();
|
|
47
|
+
let targetGender = null;
|
|
48
|
+
|
|
49
|
+
// Detect gender directory
|
|
50
|
+
if (dirNameLower === 'male' || dirNameLower === 'm' || dirNameLower.startsWith('male_') || dirNameLower.startsWith('m_')) {
|
|
51
|
+
targetGender = 'male';
|
|
52
|
+
} else if (dirNameLower === 'female' || dirNameLower === 'f' || dirNameLower.startsWith('female_') || dirNameLower.startsWith('f_')) {
|
|
53
|
+
targetGender = 'female';
|
|
54
|
+
}
|
|
39
55
|
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
|
|
56
|
+
if (targetGender) {
|
|
57
|
+
// Scan subdirectories within gender directory
|
|
58
|
+
const genderDir = path.join(dir, item.name);
|
|
59
|
+
const subItems = fs.readdirSync(genderDir, { withFileTypes: true });
|
|
60
|
+
|
|
61
|
+
for (const subItem of subItems) {
|
|
62
|
+
if (subItem.isDirectory()) {
|
|
63
|
+
const subDir = path.join(genderDir, subItem.name);
|
|
64
|
+
const subFiles = fs.readdirSync(subDir)
|
|
65
|
+
.filter(file => file.toLowerCase().endsWith('.fbx'))
|
|
66
|
+
.map(file => `/animations/${item.name}/${subItem.name}/${file}`);
|
|
67
|
+
|
|
68
|
+
if (subFiles.length > 0) {
|
|
69
|
+
const groupName = subItem.name;
|
|
70
|
+
if (!genderGroups[targetGender][groupName]) {
|
|
71
|
+
genderGroups[targetGender][groupName] = [];
|
|
72
|
+
}
|
|
73
|
+
genderGroups[targetGender][groupName].push(...subFiles);
|
|
74
|
+
console.log(`Found ${subFiles.length} ${targetGender} animations in ${item.name}/${subItem.name}/`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
// Shared directory - scan normally
|
|
80
|
+
const fullPath = path.join(dir, item.name);
|
|
81
|
+
const subFiles = fs.readdirSync(fullPath)
|
|
82
|
+
.filter(file => file.toLowerCase().endsWith('.fbx'))
|
|
83
|
+
.map(file => `/animations/${item.name}/${file}`);
|
|
84
|
+
|
|
85
|
+
if (subFiles.length > 0) {
|
|
86
|
+
groups[item.name] = subFiles;
|
|
87
|
+
console.log(`Found ${subFiles.length} shared animations in ${item.name}/`);
|
|
88
|
+
}
|
|
43
89
|
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
// Structure: animations/talking/, animations/idle/ (no gender separation)
|
|
93
|
+
for (const item of items) {
|
|
94
|
+
const fullPath = path.join(dir, item.name);
|
|
95
|
+
|
|
96
|
+
if (item.isDirectory()) {
|
|
97
|
+
const subFiles = fs.readdirSync(fullPath)
|
|
98
|
+
.filter(file => file.toLowerCase().endsWith('.fbx'))
|
|
99
|
+
.map(file => `/animations/${item.name}/${file}`);
|
|
100
|
+
|
|
101
|
+
if (subFiles.length > 0) {
|
|
102
|
+
groups[item.name] = subFiles;
|
|
103
|
+
console.log(`Found ${subFiles.length} animations in ${item.name}/`);
|
|
104
|
+
}
|
|
105
|
+
} else if (item.isFile() && item.name.toLowerCase().endsWith('.fbx')) {
|
|
106
|
+
const groupName = 'default';
|
|
107
|
+
if (!groups[groupName]) {
|
|
108
|
+
groups[groupName] = [];
|
|
109
|
+
}
|
|
110
|
+
groups[groupName].push(`/animations/${item.name}`);
|
|
49
111
|
}
|
|
50
|
-
groups[groupName].push(`/animations/${item.name}`);
|
|
51
112
|
}
|
|
52
113
|
}
|
|
53
114
|
|
|
115
|
+
// Add gender-specific groups to main groups object if found
|
|
116
|
+
if (Object.keys(genderGroups.male).length > 0 || Object.keys(genderGroups.female).length > 0) {
|
|
117
|
+
groups._genderSpecific = {
|
|
118
|
+
male: genderGroups.male,
|
|
119
|
+
female: genderGroups.female,
|
|
120
|
+
shared: genderGroups.shared
|
|
121
|
+
};
|
|
122
|
+
console.log(`\nš Gender-specific groups:`);
|
|
123
|
+
console.log(` Male: ${Object.keys(genderGroups.male).length} groups`);
|
|
124
|
+
console.log(` Female: ${Object.keys(genderGroups.female).length} groups`);
|
|
125
|
+
}
|
|
126
|
+
|
|
54
127
|
return groups;
|
|
55
128
|
}
|
|
56
129
|
|
|
@@ -89,6 +89,14 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
89
89
|
const manifestAnimations = await loadAnimationsFromManifest(animations.manifest);
|
|
90
90
|
setLoadedAnimations(manifestAnimations);
|
|
91
91
|
console.log('Loaded animations from manifest:', manifestAnimations);
|
|
92
|
+
|
|
93
|
+
// Log gender-specific groups if available
|
|
94
|
+
if (manifestAnimations._genderSpecific) {
|
|
95
|
+
console.log('Gender-specific animations detected:', {
|
|
96
|
+
male: Object.keys(manifestAnimations._genderSpecific.male || {}),
|
|
97
|
+
female: Object.keys(manifestAnimations._genderSpecific.female || {})
|
|
98
|
+
});
|
|
99
|
+
}
|
|
92
100
|
} catch (error) {
|
|
93
101
|
console.error('Failed to load animation manifest:', error);
|
|
94
102
|
setLoadedAnimations(animations);
|
|
@@ -250,13 +258,29 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
250
258
|
}
|
|
251
259
|
}, []);
|
|
252
260
|
|
|
253
|
-
// Helper function to get random animation from a group
|
|
261
|
+
// Helper function to get random animation from a group (with gender support)
|
|
254
262
|
const getRandomAnimation = useCallback((groupName) => {
|
|
255
263
|
if (!loadedAnimations || !loadedAnimations[groupName]) {
|
|
256
264
|
return null;
|
|
257
265
|
}
|
|
258
266
|
|
|
259
|
-
|
|
267
|
+
let group = loadedAnimations[groupName];
|
|
268
|
+
|
|
269
|
+
// Check if gender-specific animations are available
|
|
270
|
+
if (loadedAnimations._genderSpecific) {
|
|
271
|
+
const avatarGender = avatarBody?.toUpperCase() || 'F';
|
|
272
|
+
const genderKey = avatarGender === 'M' ? 'male' : 'female';
|
|
273
|
+
const genderGroups = loadedAnimations._genderSpecific[genderKey];
|
|
274
|
+
|
|
275
|
+
// Try gender-specific first, fallback to shared
|
|
276
|
+
if (genderGroups && genderGroups[groupName]) {
|
|
277
|
+
group = genderGroups[groupName];
|
|
278
|
+
console.log(`Using ${genderKey} animations for "${groupName}"`);
|
|
279
|
+
} else if (loadedAnimations._genderSpecific.shared && loadedAnimations._genderSpecific.shared[groupName]) {
|
|
280
|
+
group = loadedAnimations._genderSpecific.shared[groupName];
|
|
281
|
+
console.log(`Using shared animations for "${groupName}"`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
260
284
|
|
|
261
285
|
// If it's an array, randomly select one
|
|
262
286
|
if (Array.isArray(group) && group.length > 0) {
|
|
@@ -270,7 +294,7 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
270
294
|
}
|
|
271
295
|
|
|
272
296
|
return null;
|
|
273
|
-
}, [loadedAnimations]);
|
|
297
|
+
}, [loadedAnimations, avatarBody]);
|
|
274
298
|
|
|
275
299
|
// Helper function to play random animation from a group
|
|
276
300
|
const playRandomAnimation = useCallback((groupName, disablePositionLock = false) => {
|