-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathSpeechApiReflectionHelper.cs
More file actions
83 lines (65 loc) · 3.2 KB
/
Copy pathSpeechApiReflectionHelper.cs
File metadata and controls
83 lines (65 loc) · 3.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
using System.Collections;
using System.Reflection;
using System.Speech.Synthesis;
namespace MarvinsAIRA
{
internal static class SpeechApiReflectionHelper
{
private const string PROP_VOICE_SYNTHESIZER = "VoiceSynthesizer";
private const string FIELD_INSTALLED_VOICES = "_installedVoices";
private const string ONE_CORE_VOICES_REGISTRY = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCore\Voices";
private static readonly Type ObjectTokenCategoryType = typeof( SpeechSynthesizer ).Assembly
.GetType( "System.Speech.Internal.ObjectTokens.ObjectTokenCategory" )!;
private static readonly Type VoiceInfoType = typeof( SpeechSynthesizer ).Assembly
.GetType( "System.Speech.Synthesis.VoiceInfo" )!;
private static readonly Type InstalledVoiceType = typeof( SpeechSynthesizer ).Assembly
.GetType( "System.Speech.Synthesis.InstalledVoice" )!;
public static void InjectOneCoreVoices( this SpeechSynthesizer synthesizer )
{
var voiceSynthesizer = GetProperty( synthesizer, PROP_VOICE_SYNTHESIZER );
if ( voiceSynthesizer == null ) throw new NotSupportedException( $"Property not found: {PROP_VOICE_SYNTHESIZER}" );
var installedVoices = GetField( voiceSynthesizer, FIELD_INSTALLED_VOICES ) as IList;
if ( installedVoices == null )
throw new NotSupportedException( $"Field not found or null: {FIELD_INSTALLED_VOICES}" );
if ( ObjectTokenCategoryType
.GetMethod( "Create", BindingFlags.Static | BindingFlags.NonPublic )?
.Invoke( null, new object?[] { ONE_CORE_VOICES_REGISTRY } ) is not IDisposable otc )
throw new NotSupportedException( $"Failed to call Create on {ObjectTokenCategoryType} instance" );
using ( otc )
{
if ( ObjectTokenCategoryType
.GetMethod( "FindMatchingTokens", BindingFlags.Instance | BindingFlags.NonPublic )?
.Invoke( otc, new object?[] { null, null } ) is not IList tokens )
throw new NotSupportedException( $"Failed to list matching tokens" );
foreach ( var token in tokens )
{
if ( token == null || GetProperty( token, "Attributes" ) == null ) continue;
var voiceInfo =
typeof( SpeechSynthesizer ).Assembly
.CreateInstance( VoiceInfoType.FullName!, true,
BindingFlags.Instance | BindingFlags.NonPublic, null,
new object[] { token }, null, null );
if ( voiceInfo == null )
throw new NotSupportedException( $"Failed to instantiate {VoiceInfoType}" );
var installedVoice =
typeof( SpeechSynthesizer ).Assembly
.CreateInstance( InstalledVoiceType.FullName!, true,
BindingFlags.Instance | BindingFlags.NonPublic, null,
new object[] { voiceSynthesizer, voiceInfo }, null, null );
if ( installedVoice == null )
throw new NotSupportedException( $"Failed to instantiate {InstalledVoiceType}" );
installedVoices.Add( installedVoice );
}
}
}
private static object? GetProperty( object target, string propName )
{
return target.GetType().GetProperty( propName, BindingFlags.Instance | BindingFlags.NonPublic )?.GetValue( target );
}
private static object? GetField( object target, string propName )
{
return target.GetType().GetField( propName, BindingFlags.Instance | BindingFlags.NonPublic )?.GetValue( target );
}
}
}