@sage-rsc/talking-head-react 1.6.3 → 1.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sage-rsc/talking-head-react",
3
- "version": "1.6.3",
3
+ "version": "1.6.4",
4
4
  "description": "A reusable React component for 3D talking avatars with lip-sync and text-to-speech",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -1425,7 +1425,6 @@ class TalkingHead {
1425
1425
  // Use setPoseFromTemplate which properly handles pose transitions without loops
1426
1426
  this.poseName = 'wide';
1427
1427
  this.setPoseFromTemplate(this.poseTemplates['wide'], 0); // 0ms = instant, no transition
1428
- console.log('Set initial male-appropriate pose: wide');
1429
1428
  }
1430
1429
 
1431
1430
  // Initialize FBX animation loader
@@ -2238,7 +2237,6 @@ class TalkingHead {
2238
2237
  // Dynamic import to avoid loading issues
2239
2238
  const { FBXAnimationLoader } = await import('./fbxAnimationLoader.js');
2240
2239
  this.fbxAnimationLoader = new FBXAnimationLoader(this.armature);
2241
- console.log('FBX Animation Loader initialized');
2242
2240
  } catch (error) {
2243
2241
  console.warn('FBX Animation Loader not available:', error);
2244
2242
  this.fbxAnimationLoader = null;
@@ -2257,7 +2255,6 @@ class TalkingHead {
2257
2255
  this.avatar.bodyMovement = movement;
2258
2256
  }
2259
2257
 
2260
- console.log('Body movement set to:', movement);
2261
2258
 
2262
2259
  // Respect the current showFullAvatar setting instead of forcing it to true
2263
2260
  // Only unlock position when returning to idle
@@ -2278,18 +2275,11 @@ class TalkingHead {
2278
2275
  async applyBodyMovementAnimation() {
2279
2276
  // Check if avatar is ready
2280
2277
  if (!this.armature || !this.animQueue) {
2281
- console.log('Avatar not ready for body movement animations');
2282
2278
  return;
2283
2279
  }
2284
2280
 
2285
- console.log('Avatar is running:', this.isRunning);
2286
- console.log('Animation queue exists:', !!this.animQueue);
2287
-
2288
2281
  // Remove existing body movement animations
2289
- const beforeLength = this.animQueue.length;
2290
2282
  this.animQueue = this.animQueue.filter(anim => !anim.template.name.startsWith('bodyMovement'));
2291
- const afterLength = this.animQueue.length;
2292
- console.log(`Filtered animation queue: ${beforeLength} -> ${afterLength} animations`);
2293
2283
 
2294
2284
  if (this.bodyMovement === 'idle') {
2295
2285
  // Stop FBX animations if any
@@ -2303,16 +2293,14 @@ class TalkingHead {
2303
2293
  if (this.fbxAnimationLoader) {
2304
2294
  try {
2305
2295
  await this.fbxAnimationLoader.playGestureAnimation(this.bodyMovement, this.movementIntensity);
2306
- console.log('Applied FBX body movement animation:', this.bodyMovement);
2307
2296
  return; // Successfully applied FBX animation
2308
2297
  } catch (error) {
2309
- console.warn('FBX animation failed, falling back to code animation:', error);
2298
+ // Silent fallback
2310
2299
  }
2311
2300
  }
2312
2301
 
2313
2302
  // Fallback to code-based animations
2314
2303
  const movementAnim = this.createBodyMovementAnimation(this.bodyMovement);
2315
- console.log('Created movement animation:', movementAnim);
2316
2304
  if (movementAnim) {
2317
2305
  try {
2318
2306
  // Use animFactory to create proper animation object
@@ -2321,9 +2309,6 @@ class TalkingHead {
2321
2309
  // Validate the animation object before adding
2322
2310
  if (animObj && animObj.ts && animObj.ts.length > 0) {
2323
2311
  this.animQueue.push(animObj);
2324
- console.log('Applied code-based body movement animation:', this.bodyMovement);
2325
- console.log('Animation queue length:', this.animQueue.length);
2326
- console.log('Animation object:', animObj);
2327
2312
  } else {
2328
2313
  console.error('Invalid animation object created for:', this.bodyMovement);
2329
2314
  console.error('Animation object:', animObj);
@@ -2350,7 +2335,6 @@ class TalkingHead {
2350
2335
  y: this.armature.position.y,
2351
2336
  z: this.armature.position.z
2352
2337
  };
2353
- console.log('Original position stored:', this.originalPosition);
2354
2338
  }
2355
2339
 
2356
2340
  // Lock the avatar at its CURRENT position (don't move it)
@@ -2360,7 +2344,6 @@ class TalkingHead {
2360
2344
  z: this.armature.position.z
2361
2345
  };
2362
2346
 
2363
- console.log('Avatar position locked at current position:', this.lockedPosition);
2364
2347
  }
2365
2348
 
2366
2349
  /**
@@ -2374,15 +2357,12 @@ class TalkingHead {
2374
2357
  this.originalPosition.y,
2375
2358
  this.originalPosition.z
2376
2359
  );
2377
- console.log('Avatar position restored to original:', this.originalPosition);
2378
2360
  } else if (this.armature) {
2379
2361
  // Fallback: reset to center if no original position was stored
2380
2362
  this.armature.position.set(0, 0, 0);
2381
- console.log('Avatar position reset to center (0,0,0)');
2382
2363
  }
2383
2364
  this.lockedPosition = null;
2384
2365
  this.originalPosition = null; // Clear original position after unlock
2385
- console.log('Avatar position unlocked');
2386
2366
  }
2387
2367
 
2388
2368
  /**
@@ -2629,7 +2609,6 @@ class TalkingHead {
2629
2609
  this.avatar.movementIntensity = this.movementIntensity;
2630
2610
  }
2631
2611
 
2632
- console.log('Movement intensity set to:', this.movementIntensity);
2633
2612
 
2634
2613
  // Update FBX animation intensity if available
2635
2614
  if (this.fbxAnimationLoader) {
@@ -2652,18 +2631,14 @@ class TalkingHead {
2652
2631
  this.avatar.showFullAvatar = show;
2653
2632
  }
2654
2633
 
2655
- console.log('Show full avatar set to:', show);
2656
2634
 
2657
2635
  // Only change camera view if it's not already set to the desired view
2658
2636
  // This prevents the avatar from sliding down when starting animations
2659
2637
  if (show && this.viewName !== 'full') {
2660
- console.log('Changing camera view to full');
2661
2638
  this.setView('full');
2662
2639
  } else if (!show && this.viewName !== 'upper') {
2663
- console.log('Changing camera view to upper');
2664
2640
  this.setView('upper');
2665
2641
  } else {
2666
- console.log('Camera view already set to:', this.viewName);
2667
2642
  }
2668
2643
  }
2669
2644
 
@@ -2790,7 +2765,6 @@ class TalkingHead {
2790
2765
  if ( a.hasOwnProperty(this.stateName) ) {
2791
2766
  // Debug: Log state selection
2792
2767
  if (this.stateName === 'speaking' || this.stateName === 'idle') {
2793
- console.log('Selected state:', this.stateName, 'for avatar body:', this.avatar?.body);
2794
2768
  }
2795
2769
  a = a[this.stateName];
2796
2770
  } else if ( a.hasOwnProperty(this.moodName) ) {
@@ -2801,7 +2775,6 @@ class TalkingHead {
2801
2775
  a = a[this.viewName];
2802
2776
  } else if ( this.avatar && this.avatar.body && a.hasOwnProperty(this.avatar.body) ) {
2803
2777
  // Debug: Log gender-specific override
2804
- console.log('Applying gender-specific override:', this.avatar.body, 'for state:', this.stateName, 'keys:', Object.keys(a));
2805
2778
  a = a[this.avatar.body];
2806
2779
  } else if ( a.hasOwnProperty('alt') ) {
2807
2780
 
@@ -2823,7 +2796,6 @@ class TalkingHead {
2823
2796
  a = b;
2824
2797
  // Debug: Log selected alternative and check for gender override
2825
2798
  if (this.avatar && this.avatar.body && a.hasOwnProperty(this.avatar.body)) {
2826
- console.log('Found gender override in selected alternative:', this.avatar.body, 'keys:', Object.keys(a));
2827
2799
  }
2828
2800
  // Continue loop to check for gender-specific override after selecting alternative
2829
2801
  continue;
@@ -2859,7 +2831,6 @@ class TalkingHead {
2859
2831
  // Values
2860
2832
  // Debug: Log pose selection
2861
2833
  if (a.vs && a.vs.pose) {
2862
- console.log('Pose being selected from vs.pose:', a.vs.pose, 'for avatar body:', this.avatar?.body);
2863
2834
  }
2864
2835
  for( let [mt,vs] of Object.entries(a.vs) ) {
2865
2836
  const base = this.getBaselineValue(mt);
@@ -2872,7 +2843,6 @@ class TalkingHead {
2872
2843
  } else if ( typeof x === 'string' || x instanceof String ) {
2873
2844
  // Intercept pose values and override 'hip' and 'side' to 'wide' for male avatars
2874
2845
  if (mt === 'pose' && this.avatar && this.avatar.body === 'M' && (x === 'hip' || x === 'side')) {
2875
- console.log('Intercepting pose', x, 'in animation factory, overriding to wide for male avatar');
2876
2846
  return 'wide'; // Always use 'wide' for male avatars, never 'side' or 'hip'
2877
2847
  }
2878
2848
  return x.slice();
@@ -3171,12 +3141,10 @@ class TalkingHead {
3171
3141
  // Always override 'hip' and 'side' to 'wide' for male avatars
3172
3142
  if (this.poseTemplates['wide']) {
3173
3143
  j = 'wide';
3174
- console.log('Overriding pose', j === 'hip' ? 'hip' : 'side', 'to wide for male avatar');
3175
3144
  }
3176
3145
  }
3177
3146
  }
3178
3147
  this.poseName = j;
3179
- console.log('Setting pose to:', this.poseName, 'for avatar body:', this.avatar?.body, 'state:', this.stateName);
3180
3148
  this.setPoseFromTemplate( this.poseTemplates[ this.poseName ] );
3181
3149
  break;
3182
3150
 
@@ -3423,7 +3391,6 @@ class TalkingHead {
3423
3391
 
3424
3392
  if (module && module[className]) {
3425
3393
  this.lipsync[lang] = new module[className];
3426
- console.log(`Loaded lip-sync module for ${lang}`);
3427
3394
  } else {
3428
3395
  console.warn(`Lip-sync module for ${lang} not found. Available modules:`, Object.keys(LIPSYNC_MODULES));
3429
3396
  }
@@ -3946,15 +3913,6 @@ class TalkingHead {
3946
3913
  const processedText = this.lipsyncPreProcessText(text, lipsyncLang);
3947
3914
  const lipsyncData = this.lipsyncWordsToVisemes(processedText, lipsyncLang);
3948
3915
 
3949
- console.log('Browser TTS Lip-sync Debug:', {
3950
- text,
3951
- lipsyncLang,
3952
- processedText,
3953
- lipsyncData,
3954
- hasVisemes: lipsyncData && lipsyncData.visemes && lipsyncData.visemes.length > 0,
3955
- estimatedDuration
3956
- });
3957
-
3958
3916
  // Generate lip-sync animation from the viseme data
3959
3917
  const lipsyncAnim = [];
3960
3918
  if (lipsyncData && lipsyncData.visemes && lipsyncData.visemes.length > 0) {
@@ -4044,7 +4002,6 @@ class TalkingHead {
4044
4002
  const audioBuffer = await this.audioCtx.decodeAudioData(audioArrayBuffer);
4045
4003
 
4046
4004
  // Use text-based lip-sync with proper error handling
4047
- console.log('Using text-based lip-sync for debugging...');
4048
4005
  const lipsyncLang = this.avatar.lipsyncLang || this.opt.lipsyncLang || 'en';
4049
4006
 
4050
4007
  let audioAnalysis;
@@ -4117,22 +4074,9 @@ class TalkingHead {
4117
4074
  };
4118
4075
  }
4119
4076
 
4120
- console.log('ElevenLabs TTS Audio Analysis:', {
4121
- text,
4122
- audioDuration: audioBuffer.duration,
4123
- visemeCount: audioAnalysis.visemes ? audioAnalysis.visemes.length : 0,
4124
- wordCount: audioAnalysis.words ? audioAnalysis.words.length : 0,
4125
- features: {
4126
- onsets: audioAnalysis.features && audioAnalysis.features.onsets ? audioAnalysis.features.onsets.length : 0,
4127
- boundaries: audioAnalysis.features && audioAnalysis.features.phonemeBoundaries ? audioAnalysis.features.phonemeBoundaries.length : 0
4128
- },
4129
- visemes: audioAnalysis.visemes ? audioAnalysis.visemes.slice(0, 3) : [] // Show first 3 visemes for debugging
4130
- });
4131
-
4132
4077
  // Generate precise lip-sync animation from audio analysis
4133
4078
  const lipsyncAnim = [];
4134
4079
  if (audioAnalysis.visemes && audioAnalysis.visemes.length > 0) {
4135
- console.log('ElevenLabs: Generating lip-sync animation from', audioAnalysis.visemes.length, 'visemes');
4136
4080
  for (let i = 0; i < audioAnalysis.visemes.length; i++) {
4137
4081
  const visemeData = audioAnalysis.visemes[i];
4138
4082
  const time = visemeData.startTime * 1000; // Convert to milliseconds
@@ -4147,14 +4091,12 @@ class TalkingHead {
4147
4091
  }
4148
4092
  });
4149
4093
  }
4150
- console.log('ElevenLabs: Generated', lipsyncAnim.length, 'lip-sync animation frames');
4151
4094
  } else {
4152
4095
  console.warn('ElevenLabs: No visemes available for lip-sync animation');
4153
4096
  }
4154
4097
 
4155
4098
  // Combine original animation with lip-sync animation
4156
4099
  const combinedAnim = [...line.anim, ...lipsyncAnim];
4157
- console.log('ElevenLabs: Combined animation frames:', combinedAnim.length, '(original:', line.anim.length, '+ lipsync:', lipsyncAnim.length, ')');
4158
4100
 
4159
4101
  // Add to playlist
4160
4102
  this.audioPlaylist.push({ anim: combinedAnim, audio: audioBuffer });
@@ -4197,7 +4139,6 @@ class TalkingHead {
4197
4139
  const audioBuffer = await this.audioCtx.decodeAudioData(audioArrayBuffer);
4198
4140
 
4199
4141
  // Use text-based lip-sync with proper error handling
4200
- console.log('Using text-based lip-sync for Deepgram...');
4201
4142
  const lipsyncLang = this.avatar.lipsyncLang || this.opt.lipsyncLang || 'en';
4202
4143
 
4203
4144
  let audioAnalysis;
@@ -4270,22 +4211,9 @@ class TalkingHead {
4270
4211
  };
4271
4212
  }
4272
4213
 
4273
- console.log('Deepgram TTS Audio Analysis:', {
4274
- text,
4275
- audioDuration: audioBuffer.duration,
4276
- visemeCount: audioAnalysis.visemes ? audioAnalysis.visemes.length : 0,
4277
- wordCount: audioAnalysis.words ? audioAnalysis.words.length : 0,
4278
- features: {
4279
- onsets: audioAnalysis.features && audioAnalysis.features.onsets ? audioAnalysis.features.onsets.length : 0,
4280
- boundaries: audioAnalysis.features && audioAnalysis.features.phonemeBoundaries ? audioAnalysis.features.phonemeBoundaries.length : 0
4281
- },
4282
- visemes: audioAnalysis.visemes ? audioAnalysis.visemes.slice(0, 3) : [] // Show first 3 visemes for debugging
4283
- });
4284
-
4285
4214
  // Generate precise lip-sync animation from audio analysis
4286
4215
  const lipsyncAnim = [];
4287
4216
  if (audioAnalysis.visemes && audioAnalysis.visemes.length > 0) {
4288
- console.log('Deepgram: Generating lip-sync animation from', audioAnalysis.visemes.length, 'visemes');
4289
4217
  for (let i = 0; i < audioAnalysis.visemes.length; i++) {
4290
4218
  const visemeData = audioAnalysis.visemes[i];
4291
4219
  const time = visemeData.startTime * 1000; // Convert to milliseconds
@@ -4300,14 +4228,12 @@ class TalkingHead {
4300
4228
  }
4301
4229
  });
4302
4230
  }
4303
- console.log('Deepgram: Generated', lipsyncAnim.length, 'lip-sync animation frames');
4304
4231
  } else {
4305
4232
  console.warn('Deepgram: No visemes available for lip-sync animation');
4306
4233
  }
4307
4234
 
4308
4235
  // Combine original animation with lip-sync animation
4309
4236
  const combinedAnim = [...line.anim, ...lipsyncAnim];
4310
- console.log('Deepgram: Combined animation frames:', combinedAnim.length, '(original:', line.anim.length, '+ lipsync:', lipsyncAnim.length, ')');
4311
4237
 
4312
4238
  // Add to playlist
4313
4239
  this.audioPlaylist.push({ anim: combinedAnim, audio: audioBuffer });
@@ -4354,19 +4280,9 @@ class TalkingHead {
4354
4280
  const audioBuffer = await this.audioCtx.decodeAudioData(audioArrayBuffer);
4355
4281
 
4356
4282
  // Analyze audio for precise lip-sync timing
4357
- console.log('Analyzing audio for precise lip-sync...');
4358
4283
  const audioAnalysis = await this.audioAnalyzer.analyzeAudio(audioBuffer, text);
4359
4284
 
4360
- console.log('Azure TTS Audio Analysis:', {
4361
- text,
4362
- audioDuration: audioBuffer.duration,
4363
- visemeCount: audioAnalysis.visemes.length,
4364
- wordCount: audioAnalysis.words.length,
4365
- features: {
4366
- onsets: audioAnalysis.features.onsets.length,
4367
- boundaries: audioAnalysis.features.phonemeBoundaries.length
4368
- }
4369
- });
4285
+ // Azure TTS (debug disabled - removed verbose logs)
4370
4286
 
4371
4287
  // Generate precise lip-sync animation from audio analysis
4372
4288
  const lipsyncAnim = [];
@@ -5476,9 +5392,6 @@ class TalkingHead {
5476
5392
  // Lock position IMMEDIATELY to prevent any position changes (unless disabled)
5477
5393
  if (!disablePositionLock) {
5478
5394
  this.lockAvatarPosition();
5479
- console.log('Position locked immediately before FBX animation:', url);
5480
- } else {
5481
- console.log('Position locking disabled for FBX animation:', url);
5482
5395
  }
5483
5396
 
5484
5397
  let item = this.animClips.find( x => x.url === url+'-'+ndx );
@@ -5501,9 +5414,7 @@ class TalkingHead {
5501
5414
  // Use existing mixer or create new one if none exists
5502
5415
  if (!this.mixer) {
5503
5416
  this.mixer = new THREE.AnimationMixer(this.armature);
5504
- console.log('Created new mixer for FBX animation');
5505
5417
  } else {
5506
- console.log('Using existing mixer for FBX animation, preserving morph targets');
5507
5418
  }
5508
5419
 
5509
5420
  // Store callback for when animation finishes
@@ -5799,11 +5710,7 @@ class TalkingHead {
5799
5710
  fbxBoneNames.add(trackParts[0]);
5800
5711
  });
5801
5712
 
5802
- console.log('=== Ready Player Me Animation Bone Analysis ===');
5803
- console.log('FBX bone names:', Array.from(fbxBoneNames).sort().join(', '));
5804
- console.log('Avatar skeleton bone names:', Array.from(availableBones).sort().join(', '));
5805
-
5806
- // Check for arm bones specifically
5713
+ // Check for arm bones specifically (for mapping)
5807
5714
  const fbxArmBones = Array.from(fbxBoneNames).filter(b =>
5808
5715
  b.toLowerCase().includes('arm') ||
5809
5716
  b.toLowerCase().includes('hand') ||
@@ -5814,8 +5721,6 @@ class TalkingHead {
5814
5721
  b.includes('Hand') ||
5815
5722
  b.includes('Shoulder')
5816
5723
  );
5817
- console.log('FBX arm/hand/shoulder bones:', fbxArmBones.sort().join(', '));
5818
- console.log('Avatar arm/hand/shoulder bones:', avatarArmBones.sort().join(', '));
5819
5724
 
5820
5725
  // Filter and map animation tracks
5821
5726
  const mappedTracks = [];
@@ -5866,34 +5771,11 @@ class TalkingHead {
5866
5771
  }
5867
5772
  });
5868
5773
 
5869
- if (filteredShoulderTracks > 0) {
5870
- console.log(`✓ Filtered out ${filteredShoulderTracks} shoulder rotation track(s) to prevent high shoulders`);
5871
- }
5872
-
5873
- if (unmappedBones.size > 0) {
5874
- console.warn(`⚠️ ${unmappedBones.size} bone(s) could not be mapped:`, Array.from(unmappedBones).sort().join(', '));
5875
- }
5876
-
5877
5774
  // Use mapped tracks if we have any, otherwise use original
5878
5775
  if (mappedTracks.length > 0) {
5879
5776
  anim = new THREE.AnimationClip(anim.name, anim.duration, mappedTracks);
5880
- console.log(`✓ Created animation with ${mappedTracks.length} mapped tracks (from ${anim.tracks.length} original tracks)`);
5881
- if (boneNameMap.size > 0) {
5882
- console.log(`✓ Mapped ${boneNameMap.size} bone(s):`,
5883
- Array.from(boneNameMap.entries()).map(([from, to]) => `${from}→${to}`).join(', '));
5884
- }
5885
-
5886
- // Check if arm bones were mapped
5887
- const mappedArmBones = Array.from(boneNameMap.values()).filter(b =>
5888
- b.includes('Arm') || b.includes('Hand') || b.includes('Shoulder')
5889
- );
5890
- if (mappedArmBones.length > 0) {
5891
- console.log(`✓ Arm bones mapped: ${mappedArmBones.join(', ')}`);
5892
- } else {
5893
- console.warn('⚠️ No arm bones were mapped! This may cause arm rigging issues.');
5894
- }
5895
5777
  } else {
5896
- console.error('No tracks could be mapped! Animation may not work correctly.');
5778
+ console.error('No tracks could be mapped! Animation may not work correctly.');
5897
5779
  }
5898
5780
 
5899
5781
  // Rename and scale Mixamo tracks, create a pose
@@ -5955,22 +5837,18 @@ class TalkingHead {
5955
5837
  if (this.currentFBXAction) {
5956
5838
  this.currentFBXAction.stop();
5957
5839
  this.currentFBXAction = null;
5958
- console.log('FBX animation action stopped, mixer preserved for lip-sync');
5959
5840
  }
5960
5841
 
5961
5842
  // Only destroy mixer if no other animations are running
5962
5843
  // This allows morph target animations (lip-sync) to continue
5963
5844
  if (this.mixer && this.mixer._actions.length === 0) {
5964
5845
  this.mixer = null;
5965
- console.log('Mixer destroyed as no actions remain');
5966
5846
  }
5967
5847
 
5968
5848
  // Unlock position when animation stops (only if it was locked)
5969
5849
  if (this.positionWasLocked) {
5970
5850
  this.unlockAvatarPosition();
5971
- console.log('Position unlocked after FBX animation stopped');
5972
5851
  } else {
5973
- console.log('Position was not locked, no unlock needed');
5974
5852
  }
5975
5853
 
5976
5854
  // Restart gesture