Sunday 31 December 2023

Password hashing in .NET

This article will look on different ways to hash a password in .NET. MD5 was developed by Ron Rivest in 1991 and was used a lot in the 90s, but in 2005 it was revealed it contains collisions. MD5 and SHA-1 is not advised to used in sensitive hashing related to security anymore. Instead, a PBKDF or Password Derived Key-derivation function algorithm will be used. A PBKDF2-based method in Rfc2898DeriveBytes will be used. It has been available since .NET 6. Users of Asp.net Core Identity are recommended to use PasswordHasher instead : https://andrewlock.net/exploring-the-asp-net-core-identity-passwordhasher/ An overview of the arithmetic flow of PBKDF2 is shown below. In the diagram, SHA-512 is indicated, but the code shown in this article
uses SHA-256.

First off, to do a MD5 hash we can use the following :
 
 
 static string Md5(string input){
	using (var md5 = MD5.Create()){
		var byteHash = md5.ComputeHash(Encoding.UTF8.GetBytes(input)); 
		var hash = BitConverter.ToString(byteHash).Replace("-", "");
		return hash;
	}
}
 
 
And to test it out we can run the following:
 
 
 void Md5Demo()
{
	string inputPassword = "abc123";
	string md5Hash = Md5(inputPassword);
	Console.WriteLine("MD5 Demonstration in .NET");
	Console.WriteLine("-------------------------");
	Console.WriteLine($"Password to hash: {inputPassword}");
	Console.WriteLine($"MD5 hashed password: {md5Hash}");
	Console.WriteLine();
} 
 
 

MD5 Demonstration in .NET ------------------------- Password to hash: abc123 MD5 hashed password: E99A18C428CB38D5F260853678922E03 The MD5 hash above agrees with the online MD5 hash here: https://www.md5hashgenerator.com/ MD5 method here does not mention any salt, but this could be concatenated with the password to prevent against rainbow table attacks, that is dictionary attacks. Next, to perform PDKDF2 hashing, the code below can be used. Note that this algorithm will be run iteratively to generate a hash value that is increasingly more computationally expensive to calculate the hash of compared to the number of iterations and includes a salt, making it scalable
to be more and more difficult for attacks.


static byte[] _salt = RandomNumberGenerator.GetBytes(32);

static void HashPassword(string passwordToHash, int numberOfRounds)
{
	var sw = Stopwatch.StartNew();
	var hashedPassword = Rfc2898DeriveBytes.Pbkdf2(
		passwordToHash,
		_salt,
		numberOfRounds,
		HashAlgorithmName.SHA256,
		32);
	sw.Stop();

	Console.WriteLine();
	Console.WriteLine("Password to hash : " + passwordToHash);
	Console.WriteLine("Hashed Password : " + Convert.ToBase64String(hashedPassword));
	Console.WriteLine("Iterations < " + numberOfRounds + "> Elapsed Time: " + sw.ElapsedMilliseconds + " ms");
}


The value 32 here is the desired output length of the hash, we can decide how long the hash we get out of the call to the method. We can then test out the Pbkdf2 method using an increasing number of iterations.
 
 
 void RunPbkdf2HashDemo()
{
	const string passwordToHash = "abc123";

	Console.WriteLine("Password Based Key Derivation Function Demonstration in .NET");
	Console.WriteLine("------------------------------------------------------------");
	Console.WriteLine();
	Console.WriteLine("PBKDF2 Hashes using Rfc2898DeriveBytes");
	Console.WriteLine();

	HashPassword(passwordToHash, 1);
	HashPassword(passwordToHash, 10);
	HashPassword(passwordToHash, 100);
	HashPassword(passwordToHash, 1000);
	HashPassword(passwordToHash, 10000);
	HashPassword(passwordToHash, 100000);
	HashPassword(passwordToHash, 1000000);
	HashPassword(passwordToHash, 5000000);
} 
 
 
This gives the following output:
 
 
Password Based Key Derivation Function Demonstration in .NET
------------------------------------------------------------

PBKDF2 Hashes using Rfc2898DeriveBytes

Password to hash : abc123
Hashed Password : eqeul5z7l2dPrOo8WjH/oTt0RYHvlZ2lvk8SUoTjZq4=
Iterations (1) Elapsed Time: 0 ms

Password to hash : abc123
Hashed Password : wfd8qQobzBPZvdemqrtZczqctFe0JeAkKjU3IJ48cms=
Iterations (10) Elapsed Time: 0 ms

Password to hash : abc123
Hashed Password : VY45SxzhqjYronha0kt1mQx+JRDVlXj82prX3H7kjII=
Iterations (100) Elapsed Time: 0 ms

Password to hash : abc123
Hashed Password : B0LfHgRSslG/lWe7hbp4jb8dEqQ/bZwNtxsaqbVBZ2I=
Iterations (1000) Elapsed Time: 0 ms

Password to hash : abc123
Hashed Password : LAHwpS4bnbO7CQ1r7buYgUTrp10FyaRyeK6hCwGwv20=
Iterations (10000) Elapsed Time: 1 ms

Password to hash : abc123
Hashed Password : WDjyPySpULXtVOVmSR9cYlzAY4LWeJqDBhszKAfIaPc=
Iterations (100000) Elapsed Time: 13 ms

Password to hash : abc123
Hashed Password : sDx6sOrTl2b7cNZGUAecg7YO4Md/g3eAtfQSvh/vxpM=
Iterations (1000000) Elapsed Time: 127 ms

Password to hash : abc123
Hashed Password : ruywLaR0QApOU5bkqE/x2AAhYJzBj5y6D3P3IxlIF2I=
Iterations (5000000) Elapsed Time: 643 ms
 
 
Note that it takes many iterations before the computation takes significant time. Sources / links :

AES Encryption with Galois Counter Mode (GCM) in C#

This article presents some helper methods for performing AES Encryption using Galois Counter Mode (GCM). AES or Advanced Encryption Standard is the most used encryption algorithm used today, having overtaken DES and Triple DES since 2001. We will look into the GCM mode of AES in this article. AES-GCM class AesGcm is supported in .NET Core 3.0 and newer .NET versions, plus in .NET Standard 2.1. AES-GCM is authenticated encryption, compared to default AES-CBC (Cipher Block Chaining). Benefits of using GCM mode of AES is the following:
  • Data authenticity / integrity. This is provided via a tag that is outputted by the encryption and used while decrypting
  • Provides support for sending additional data, used for example in newer TLS implementations to provide both encryption and a non-encrypted payload. This is called additional metadata
Here is a helper class to perform encryption and decryption using AES-GCM.
 
 public static class AesGcmEncryption {


	public static (byte[], byte[]) Encrypt(byte[] dataToEncrypt, byte[] key, byte[] nonce, byte[] associatedData = null)
	{
		using var aesGcm = new AesGcm(key);
		//tag and ciphertext will be filled during encryption
		var tag = new byte[16]; //tag is a hmac (hash-based message authentication code) to check that information has not been tampered with
	    var cipherText = new byte[dataToEncrypt.Length];
		aesGcm.Encrypt(nonce, dataToEncrypt, cipherText, tag, associatedData);
		return (cipherText, tag);
	}

	public static byte[] Decrypt(byte[] cipherText, byte[] key, byte[] nonce, byte[] tag, byte[] associatedData = null)
	{
		using var aesGcm = new AesGcm(key);
		//tag and ciphertext will be filled during encryption
		var decryptedData = new byte[cipherText.Length];
		aesGcm.Decrypt(nonce, cipherText, tag, decryptedData, associatedData);
		return decryptedData;
	}
	
}
 
 
In the code above, the encrypt method returns a tuple with the ciperText and the tag. These are the encrypted data and the tag, both must be used while decrypting and the tag provides as mentioned a means of checking the integrity of data, i.e. that data has not been tampered with. Note that the 16-byte tag and the ciphertext is filled after running the Encrypt method of the AesGcm class. The cipherText array must be the same length as the dataToEncrypt array inputted. Here is sample code to use AES-GCM. Note that the metadata used here, while optional, must match in case it is set in the encryption and decryption. The nonce must be 12 bytes - 96 bits in length.The nonce is similar to a initialization vector, although it is used once for the particular encryption and decryption, it is used to protect against replay attacks.
 
 
 void TestAesGCM()
{
	const string original = "Text to encrypt";
	var key = RandomNumberGenerator.GetBytes(32); //256 bits key
	var nonce = RandomNumberGenerator.GetBytes(12); //96 bits nonce
	
	(byte[] cipherText, byte[] tag) result = AesGcmEncryption.Encrypt(Encoding.UTF8.GetBytes(original),
	 key, nonce, Encoding.UTF8.GetBytes("some metadata 123"));
	 byte[] decryptedText = AesGcmEncryption.Decrypt(result.cipherText, key, nonce, result.tag, Encoding.UTF8.GetBytes("some metadata 123")); 
		
	Console.WriteLine("AES Encryption demo GCM - Galois Counter Mode:");
	Console.WriteLine("--------------");
	Console.WriteLine("Original Text = " + original);
	Console.WriteLine("Encrypted Text = " + Convert.ToBase64String(result.cipherText));
	Console.WriteLine("Tag = " + Convert.ToBase64String(result.tag));
	Console.WriteLine("Decrypted Text = " + Encoding.UTF8.GetString(decryptedText));
}
 
 
AES Encryption demo GCM - Galois Counter Mode: -------------- Original Text = Text to encrypt Encrypted Text = 9+2x0kctnRwiDDHBm0/H Tag = sSDxsg17HFdjE4cuqRlroQ== Decrypted Text = Text to encrypt Use AES-GCM to provide integrity checking and allowing to send in metadata if desired to encrypt and decrypting with the AES algorithm. We can protect the AES key using different methods, for example using the Data Protection API, this is only supported in Windows. Let's look at a helper class for using Data Protection API.
 
 
 public static class DataProtectionUtil {

	public static byte[] Protect(byte[] dataToEncrypt, byte[] optionalEntropy, DataProtectionScope scope)
	{
		var encryptedData = ProtectedData.Protect(dataToEncrypt, optionalEntropy, scope);
		return encryptedData;
	}
	
	public static byte[] Unprotect(byte[] encryptedData, byte[] optionalEntropy, DataProtectionScope scope){
		var decryptedData = ProtectedData.Unprotect(encryptedData, optionalEntropy, scope);
		return decryptedData;
	}

	public static string Protect(string dataToEncrypt, string optionalEntropy, DataProtectionScope scope)
	{
		var encryptedData = ProtectedData.Protect(Encoding.UTF8.GetBytes(dataToEncrypt), optionalEntropy != null ? Encoding.UTF8.GetBytes(optionalEntropy) : null, scope);
		return Convert.ToBase64String(encryptedData);
	}

	public static string Unprotect(string encryptedData, string optionalEntropy, DataProtectionScope scope)
	{
		var decryptedData = ProtectedData.Unprotect(Convert.FromBase64String(encryptedData), optionalEntropy != null ? Encoding.UTF8.GetBytes(optionalEntropy) : null, scope);
		return Encoding.UTF8.GetString(decryptedData);
	}

}
 
 

An example how to protect your AES key:

 
 
void EncryptAndDecryptWithProtectedKey(){
	var original = "Text to encrypt";
	Console.WriteLine($"Original Text = {original}");
	
	//Create key and nnoce . Encrypt our text with AES 
	var gcmKey = RandomNumberGenerator.GetBytes(32);
	var nonce = RandomNumberGenerator.GetBytes(12); 
	
	var result = EncryptText(original, gcmKey, nonce); 
	
	//Create some entropy and protect AES key
	var entropy = RandomNumberGenerator.GetBytes(16); 
	var protectedKey = ProtectedData.Protect(gcmKey, entropy, DataProtectionScope.CurrentUser);

	Console.WriteLine($"gcmKey = {Convert.ToBase64String(gcmKey)}, protectedKey = {Convert.ToBase64String(protectedKey)}");
	
	// Decrypt the text with AES. the AES key has to be retrieved with DPAPI.
	var decryptedText = DecryptText(result.encrypted, nonce, result.tag, protectedKey, entropy);

	Console.WriteLine($"Decrypted Text using AES GCM with key retrieved via Data Protection API = {decryptedText}");

}

private static (byte[] encrypted, byte[] tag) EncryptText(string original, byte[] gcmKey, byte[] nonce){
	return AesGcmEncryption.Encrypt(Encoding.UTF8.GetBytes(original), gcmKey, nonce, Encoding.UTF8.GetBytes("some meta"));
}

private static string DecryptText(byte[] encrypted, byte[] nonce, byte[] tag, byte[] protectedKey, byte[] entropy){
	
	var key = DataProtectionUtil.Unprotect(protectedKey, entropy, DataProtectionScope.CurrentUser);

	Console.WriteLine($"Inside DecryptText: gcmKey = {Convert.ToBase64String(key)}, protectedKey = {Convert.ToBase64String(protectedKey)}");

	var decryptedText = AesGcmEncryption.Decrypt(encrypted, key, nonce, tag, Encoding.UTF8.GetBytes("some meta"));
	return Encoding.UTF8.GetString(decryptedText);
}
 
Data Protection API is only supported on Windows platform, there are more possibilities to protect AES key but protecting your key is always a challenge when dealing with symmetric encryption algorithms such as AES. Some more links:

Thursday 28 December 2023

Digital signatures with RSA in .NET

I have looked at Digital signatures with RSA in .NET today. Digital signatures are used to provide non-repudiation, an authenticity proof that the original sender is who the sender claims to be and also that the data has not been hampered with. We will return a tuple of both a SHA-256 computed hash of some document data and also its digital signature using the RSA algorithm. I have used .netstandard 2.0 here, so the code can be used in most frameworks in both .NET Framework and .NET. We will use RSA here to do the digital signature signing and verification. First off, here is a helper class to create a RSA encrypted signature of a SHA-256 hash, here we create a new RSA with key size 2048. RsaDigitalSignature.cs
 
 
 public class RsaDigitalSignature
{
	private RSA _rsa;


	public RsaDigitalSignature()
	{
		_rsa = RSA.Create();
		_rsa.KeySize = 2048;
	}
	
	public static byte[] ComputeHashSha256(byte[] toBeHashed)
	{
		using (var sha256 = SHA256.Create())
		{
			return sha256.ComputeHash(toBeHashed);
		}
	}

	public (byte[] Signature, byte[] HashOfData) SignData(byte[] dataToSign)
	{
		var hashOfDataToSign = ComputeHashSha256(dataToSign);
		return (_rsa.SignHash(
			hashOfDataToSign,
			HashAlgorithmName.SHA256,
			RSASignaturePadding.Pkcs1),
			hashOfDataToSign);
	}

	public bool VerifySignature(byte[] signature, byte[] hashOfDataToSign)
	{
		return _rsa.VerifyHash(hashOfDataToSign, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
	}

}
 
 
 
In the code above, we receive some document data and create the SHA-255 hash, which is computed. We return a tuple with the signed hash from the computed SHA-256 hash and also the computed SHA-256 hash itself. A console application that runs the sample code above is the following:
 
 
 void Main()
{
	SignAndVerifyData();
	//Console.ReadLine();
}

private static void SignAndVerifyData()
{
	Console.WriteLine("RSA-based sigital signature demo");
	var document = Encoding.UTF8.GetBytes("Document to sign");	
	var digitalSignature = new RsaDigitalSignature();
	var signature = digitalSignature.SignData(document);
	bool isValidSignature = digitalSignature.VerifySignature(signature.Signature, signature.HashOfData);
	Console.WriteLine($"\nInput Document:\n{Convert.ToBase64String(document)}\nIs the digital signature valid? {isValidSignature} \nSignature: {Convert.ToBase64String(signature.Signature)} \nHash of data:\n{ Convert.ToBase64String(signature.HashOfData)}");
}
 
 
Our verification of the signature shows that the verification of the digital signature passes.
 
Input Document:
RG9jdW1lbnQgdG8gc2lnbg==
Is the digital signature valid? True
Signature: Gok1x8Wxm9u5jTRcqrgPsI45ie3WPZLi/FNbaJMGTHqBmNbpJTEYjsXix97aIF6uPjgrxQWJKCegc8S4yASdut7TpJafO9wSRqvScc2SuOGK9BqnX+9GwRRQNti8ynm0ARRar+Z4hTpYY/XngFZ+ovvqIT3KRMK/7tsMmTg87mY0KelteFX7z7G7wPB9kKjT6ORYK4lVr35fihrbxei0XQP59YuEdALy+vbvKUm3JNv4sBU0lc9ZKpp2XF0rud8UnY1Nz4/XH7ZoaKfca5HXs9yq89DJRaPBRi1+Wv41vTCf8zFKPWZJrw6rm6kBMNHMENYbeBNdZyiCspTsHZmsVA==
Hash of data:
VPPxOVW2A38lCB810vuZbBH50KQaPSCouN0+tOpYDYs=
 
The code above uses a RSA created on the fly and is not so easy to share between a sender and a receiver. Let's look at how we can use X509 certificates to do the RSA encyption. It should be possible to share the source code below between the sender and the receiver and for example
export the public part of the X509 certificate to the receiver, which the receiver could install in a certificate store, only requred to know the thumbprint of the cert which is easy to see in MMC (Microsoft Management Console) or using Powershell and cd-ing into cert:\ folder . Let's first look at a helper class to get hold of a installed X509 certificate.



public class CertStoreUtil
{
	public static System.Security.Cryptography.X509Certificates.X509Certificate2 GetCertificateFromStore(
	System.Security.Cryptography.X509Certificates.StoreLocation storeLocation,
	string thumbprint, bool validOnly = true) {
	 var store = new X509Store(storeLocation);
	 store.Open(OpenFlags.ReadOnly);
	 var cert = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly).FirstOrDefault();
	 store.Close();
	 return cert;
	}
}



Next up, a helper class to create a RSA-based digital signature like in the previous example, but using a certificate.

 
 
 public class RsaFromCertDigitalSignature
{
	private RSA _privateKey;
	private RSA _publicKey;

	public RsaFromCertDigitalSignature(StoreLocation storeLocation, string thumbprint)
	{
		_privateKey = CertStoreUtil.GetCertificateFromStore(StoreLocation.LocalMachine, thumbprint).GetRSAPrivateKey();
		_publicKey = CertStoreUtil.GetCertificateFromStore(StoreLocation.LocalMachine, thumbprint).GetRSAPrivateKey();
	}

	public static byte[] ComputeHashSha256(byte[] toBeHashed)
	{
		using (var sha256 = SHA256.Create())
		{
			return sha256.ComputeHash(toBeHashed);
		}
	}

	public (byte[] Signature, byte[] HashOfData) SignData(byte[] dataToSign)
	{
		var hashOfDataToSign = ComputeHashSha256(dataToSign);
		return (_privateKey.SignHash(
			hashOfDataToSign,
			HashAlgorithmName.SHA256,
			RSASignaturePadding.Pkcs1),
			hashOfDataToSign);
	}

	public bool VerifySignature(byte[] signature, byte[] hashOfDataToSign)
	{
		return _publicKey.VerifyHash(hashOfDataToSign, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
	}

}

 
 
A console app that tests out the code above is shown next, I have selected a random cert on my dev pc here.

 
 
 void Main()
{
	SignAndVerifyData();
	//Console.ReadLine();
}

private static void SignAndVerifyData()
{
	Console.WriteLine("RSA-based sigital signature demo");
	var document = Encoding.UTF8.GetBytes("Document to sign");

	//var x509CertLocalHost = CertStoreUtil.GetCertificateFromStore(StoreLocation.LocalMachine, "1f0b749ff936abddad89f4bbea7c30ed64e3dd07");
		
	var digitalSignatureWithCert = new RsaFromCertDigitalSignature(StoreLocation.LocalMachine, "1f0b749ff936abddad89f4bbea7c30ed64e3dd07");
	var signatureWithCert = digitalSignatureWithCert.SignData(document);
	bool isValidSignatureFromCert = digitalSignatureWithCert.VerifySignature(signatureWithCert.Signature, signatureWithCert.HashOfData);
    Console.WriteLine(
		$@"Input Document:
		{Convert.ToBase64String(document)}
		Is the digital signature signed with private key of CERT valid according to public key of CERT? {isValidSignatureFromCert}
		Signature: {Convert.ToBase64String(signatureWithCert.Signature)} 
		Hash of data:\n{Convert.ToBase64String(signatureWithCert.HashOfData)}");
}

 
 
Now here is an important concept in digital signatures :
  • For digital signatures, we MUST use a private key (e.g. private key of RSA instance, which can either be made on the fly or retrieved from for example a X509 certificate. Or a Json web key in a more modern example.
  • For digital signature, to verify a signature we can use either the public or the private key, usually just the public key (which can be shared). For X509 certiifcates, we usually share a public cert (.cert or similar format) and keep our private cert ourselves (.pfx).
Sample output of the console app shown above:
 
 RSA-based sigital signature demo
Input Document:
    RG9jdW1lbnQgdG8gc2lnbg==
    Is the digital signature signed with private key of CERT valid according to public key of CERT? True
    Signature: ZHWzJeZnwbfI109uK0T4ubq4B+CHedQPIDgPREz+Eq9BR6A9y6kQEvSrxqUHvOppSDN5kDt5bTiWv1pvDPow+czb7N6kmFf1zQUxUs3ip4WPovBtQKmfpf9/i3DNkRILcoMLdZdKnn0aSaK66f0oxkSIc4nEkb3O9PbejVso6wLqSdDCh96d71gbHqOjyiZLBj2VlqalWvEPuo9GB0s2Uz2fxtFGMUQiZvH3jKR+9F4LwvKCc1K0E/+J4Np57JSfKgmid9QyL2r7nO19SVoVL3yBY7D8UxVIRw8sT/+JKXlnyh8roK7kaxDtW4+FMK6LT/QPvi8LkiNmA+eVv3kk9w==
    Hash of data:\nVPPxOVW2A38lCB810vuZbBH50KQaPSCouN0+tOpYDYs=
 

Thursday 23 November 2023

Implementing Basic Auth in Core WCF

WCF or Windows Communication Foundation was released initially in 2006 and was an important part of .NET Framework to create serverside services. It supports a lot of different protocols, not only HTTP(S), but also Net.Tcp, Msmq, Named pipes and more.

Sadly, .NET Core 1, when released in 2016, did not include WCF. The use of WCF has been more and more replaced by REST API over HTTP(S) using JWT tokens and not SAML.

But a community driven project supported by a multitude of companies including Microsoft and Amazon Web Services has been working on the Core WCF project and this project is starting to gain some more use, also allowing companies to migrate their platform services over to .NET.

I have looked at some basic stuff though, namely Basic Auth in Core WCF, and actually there is no working code sample for this. I have tapped into the ASP.NET Core pipeline to make it work by studying different code samples which has made part of it work, and I got it working. In this article I will explain how.

I use GenericIdentity to make it work. On the client side I have this extension method where I pass the username and password inside the soap envelope. I use .net6 client and service and service use CoreWCF version 1.5.1.

Source code for demo client is here: https://github.com/toreaurstadboss/CoreWCFWebClient1

The client is an ASP.NET Core MVC client who has added a Core WCF service as a connected service, generating a ServiceClient. The same type of service reference seen in .NET Framework in other words.

Client side setup for Core WCF Basic Auth

Source code for demo service is here: https://github.com/toreaurstadboss/CoreWCFService1


Extension method WithBasicAuth:
BasicHttpBindingClientFactory.cs



using System.ServiceModel;
using System.ServiceModel.Channels;

namespace CoreWCFWebClient1.Extensions
{
    public static class BasicHttpBindingClientFactory
    {

        /// <summary>
        /// Creates a basic auth client with credentials set in header Authorization formatted as 'Basic [base64encoded username:password]'
        /// Makes it easier to perform basic auth in Asp.NET Core for WCF
        /// </summary>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        public static TServiceImplementation WithBasicAuth<TServiceContract, TServiceImplementation>(this TServiceImplementation client, string username, string password)
              where TServiceContract : class
                where TServiceImplementation : ClientBase<TServiceContract>, new()
        {
            string clientUrl = client.Endpoint.Address.Uri.ToString();

            var binding = new BasicHttpsBinding();
            binding.Security.Mode = BasicHttpsSecurityMode.Transport;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;

            string basicHeaderValue = "Basic " + Base64Encode($"{username}:{password}");
            var eab = new EndpointAddressBuilder(new EndpointAddress(clientUrl));
            eab.Headers.Add(AddressHeader.CreateAddressHeader("Authorization",  // Header Name
                string.Empty,           // Namespace
                basicHeaderValue));  // Header Value
            var endpointAddress = eab.ToEndpointAddress();

            var clientWithConfiguredBasicAuth = (TServiceImplementation) Activator.CreateInstance(typeof(TServiceImplementation), binding, endpointAddress)!;
            clientWithConfiguredBasicAuth.ClientCredentials.UserName.UserName = username;
            clientWithConfiguredBasicAuth.ClientCredentials.UserName.Password = username;

            return clientWithConfiguredBasicAuth;
        }

        private static string Base64Encode(string plainText)
        {
            var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
            return Convert.ToBase64String(plainTextBytes);
        }

    }
}

Example call inside a razor file in a .net6 web client, I made client and service from the WCF template :
Index.cshtml

@{

    string username = "someuser";
    string password = "somepassw0rd";

    var client = new ServiceClient().WithBasicAuth<IService, ServiceClient>(username, password);

    var result = await client.GetDataAsync(42);

    <h5>@Html.Raw(result)</h5>
}

I manage to set the identity via the call above, here is a screenshot showing this :

Setting up Basic Auth for serverside

Let's look at the serverside, it was created to start with as an ASP.NET Core .NET 6 with MVC Views solution. I added these Nugets to add CoreWCF, showing the entire .csproj since it also includes some important using statements :
CoreWCFService1.csproj


<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
  </PropertyGroup>
  <ItemGroup>
    <Using Include="CoreWCF" />
    <Using Include="CoreWCF.Configuration" />
    <Using Include="CoreWCF.Channels" />
    <Using Include="CoreWCF.Description" />
    <Using Include="System.Runtime.Serialization " />
    <Using Include="CoreWCFService1" />
    <Using Include="Microsoft.Extensions.DependencyInjection.Extensions" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="CoreWCF.Primitives" Version="1.5.1" />
    <PackageReference Include="CoreWCF.Http" Version="1.5.1" />
  </ItemGroup>
</Project>


Next up, in the file Program.cs different setup is added to add Basic Auth. In Program.cs , basic auth is set up in these code lines :
Program.cs

builder.Services.AddSingleton<IUserRepository, UserRepository>();

builder.Services.AddAuthentication("Basic").
            AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>
            ("Basic", null);
             
This adds authentication in services. We also make sure to add authentication itself after WebApplicationBuilder has been built, making sure also to set AllowSynchronousIO to true as usual. Below is listet the pipline setup of authentication, the StartsWithSegments should of course be adjusted in case you have multiple services:
Program.cs

app.Use(async (context, next) =>
{
    // Only check for basic auth when path is for the TransportWithMessageCredential endpoint only
    if (context.Request.Path.StartsWithSegments("/Service.svc"))
    {
        // Check if currently authenticated
        var authResult = await context.AuthenticateAsync("Basic");
        if (authResult.None)
        {
            // If the client hasn't authenticated, send a challenge to the client and complete request
            await context.ChallengeAsync("Basic");
            return;
        }
    }
    // Call the next delegate/middleware in the pipeline.
    // Either the request was authenticated of it's for a path which doesn't require basic auth
    await next(context);
});

We set up the servicemodel security like this to support transport mode security with the basic client credentials type.
Program.cs
 
app.UseServiceModel(serviceBuilder =>
{
    var basicHttpBinding = new BasicHttpBinding();
    basicHttpBinding.Security.Mode = BasicHttpSecurityMode.Transport;
    basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
    serviceBuilder.AddService<Service>(options =>
    {
        options.DebugBehavior.IncludeExceptionDetailInFaults = true;
    });
    serviceBuilder.AddServiceEndpoint<Service, IService>(basicHttpBinding, "/Service.svc");

    var serviceMetadataBehavior = app.Services.GetRequiredService<ServiceMetadataBehavior>();
    serviceMetadataBehavior.HttpsGetEnabled = true;
});
  
The BasicAuthenticationHandler looks like this:
BasicAuthenticationHandler.cs
	
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
using System.Security.Claims;
using System.Security.Principal;
using System.Text;
using System.Text.Encodings.Web;

public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{

    private readonly IUserRepository _userRepository;
    public BasicAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
        ILoggerFactory logger,
        UrlEncoder encoder,
        ISystemClock clock, IUserRepository userRepository) :
       base(options, logger, encoder, clock)
    {
        _userRepository = userRepository;
    }

    protected async override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        string? authTicketFromSoapEnvelope = await Request!.GetAuthenticationHeaderFromSoapEnvelope();

        if (authTicketFromSoapEnvelope != null && authTicketFromSoapEnvelope.StartsWith("basic", StringComparison.OrdinalIgnoreCase))
        {
            var token = authTicketFromSoapEnvelope.Substring("Basic ".Length).Trim();
            var credentialsAsEncodedString = Encoding.UTF8.GetString(Convert.FromBase64String(token));
            var credentials = credentialsAsEncodedString.Split(':');
            if (await _userRepository.Authenticate(credentials[0], credentials[1]))
            {
                var identity = new GenericIdentity(credentials[0]);
                var claimsPrincipal = new ClaimsPrincipal(identity);
                var ticket = new AuthenticationTicket(claimsPrincipal, Scheme.Name);
                return await Task.FromResult(AuthenticateResult.Success(ticket));
            }
        }

        return await Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header"));
    }

    protected override Task HandleChallengeAsync(AuthenticationProperties properties)
    {
        Response.StatusCode = 401;
        Response.Headers.Add("WWW-Authenticate", "Basic realm=\"thoushaltnotpass.com\"");
        Context.Response.WriteAsync("You are not logged in via Basic auth").Wait();
        return Task.CompletedTask;
    }

}

This authentication handler has got a flaw, if you enter the wrong password and username you get a 500 internal server error instead of the 401. I have not found out how to fix this yet.. Authenticate.Fail seems to short-circuit everything in case you enter wrong credentials. The _userRepository.Authenticate method is implemented as a dummy implementation, the user repo could for example do a database connection to look up the user via the provided credentials or some other means, maybe via ASP.NET Core MemberShipProvider ? The user repo looks like this:
(I)UserRepository.cs

  public interface IUserRepository
    {

        public Task<bool> Authenticate(string username, string password);
    }

    public class UserRepository : IUserRepository
    {
        public Task<bool> Authenticate(string username, string password)
        {
            //TODO: some dummie auth mechanism used here, make something more realistic such as DB user repo lookup or similar
            if (username == "someuser" && password == "somepassw0rd")
            {
                return Task.FromResult(true);
            }
            return Task.FromResult(false);
        }
    }
    
    
 
So I have implemented basic auth via reading out the credentials via Auth header inside soap envelope. I circumvent a lot of the Core WCF Auth by perhaps relying too much on the ASP.Net Core pipeline instead. Remember, WCF has to interop some with the ASP.NET Core pipeline to make it work properly and as long as we satisfy the demands of both the WCF and ASP.NET Core pipelines, we can make the authentication work. I managed to set the username via setting claims in the expected places of ServiceSecurityContext and CurrentPrincipal. The WCF service looks like this, note the use of the [Autorize] attribute :
Service.cs
   
public class Service : IService
 {

     [Authorize]
     public string GetData(int value)
     {
         return $"You entered: {value}. <br />The client logged in with transport security with BasicAuth with https (BasicHttpsBinding).<br /><br />The username is set inside ServiceSecurityContext.Current.PrimaryIdentity.Name: {ServiceSecurityContext.Current.PrimaryIdentity.Name}. <br /> This username is also stored inside Thread.CurrentPrincipal.Identity.Name: {Thread.CurrentPrincipal?.Identity?.Name}";
     }

     public CompositeType GetDataUsingDataContract(CompositeType composite)
     {
         if (composite == null)
         {
             throw new ArgumentNullException("composite");
         }
         if (composite.BoolValue)
         {
             composite.StringValue += "Suffix";
         }
         return composite;
     }
 }
   
I am mainly satisfied with this setup as it though is not optimal since ASP.NET Core don't seem to be able to work together with CoreWCF properly, instead we add the authentication as a soap envelope authorization header which we read out. I used some time to read out the authentication header, this is done on the serverside with the following extension method :
HttpRequestExtensions.cs
 
 
using System.IO.Pipelines;
using System.Text;
using System.Xml.Linq;

public static class HttpRequestExtensions
{

    public static async Task<string?> GetAuthenticationHeaderFromSoapEnvelope(this HttpRequest request)
    {
        ReadResult requestBodyInBytes = await request.BodyReader.ReadAsync();
        string body = Encoding.UTF8.GetString(requestBodyInBytes.Buffer.FirstSpan);
        request.BodyReader.AdvanceTo(requestBodyInBytes.Buffer.Start, requestBodyInBytes.Buffer.End);

        string authTicketFromHeader = null;

        if (body?.Contains(@"http://schemas.xmlsoap.org/soap/envelope/") == true)
        {
            XNamespace ns = "http://schemas.xmlsoap.org/soap/envelope/";
            var soapEnvelope = XDocument.Parse(body);
            var headers = soapEnvelope.Descendants(ns + "Header").ToList();

            foreach (var header in headers)
            {
                var authorizationElement = header.Element("Authorization");
                if (!string.IsNullOrWhiteSpace(authorizationElement?.Value))
                {
                    authTicketFromHeader = authorizationElement.Value;
                    break;
                }
            }
        }

        return authTicketFromHeader;
    }

} 
 
 
Note the use of BodyReader and method AdvanceTo. This was the only way to rewind the Request stream after reading the HTTP soap envelope header for Authorization, it took me hours to figure out why this failed in ASP.NET Core pipeline, until I found some tips in a Github discussion thread on Core WCF mentioning the error and a suggestion in a comment there. See more documentation about BodyWriter and BodyReader here from MVP Steve Gordon here: https://www.stevejgordon.co.uk/using-the-bodyreader-and-bodywriter-in-asp-net-core-3-0

Tuesday 21 November 2023

Increasing timeout in CoreWCF project for client

I have tested out CoreWCF a bit and it is good to see WCF once again in a modern framework such as ASP.NET Core. Here is how you can increase timeouts in CoreWCF. You can put the timeout into an appsettings file too if you want. First off, after having added a Service Reference to your WCF service. Look inside the Reference.cs file. Make note of:
  1. Namespace in the Reference.cs file
  2. Class name of the client
My client uses these Nuget packages in its csproj :
  
  
  <ItemGroup>
    <PackageReference Include="System.ServiceModel.Duplex" Version="4.10.*" />
    <PackageReference Include="System.ServiceModel.Federation" Version="4.10.*" />
    <PackageReference Include="System.ServiceModel.Http" Version="4.10.*" />
    <PackageReference Include="System.ServiceModel.NetTcp" Version="4.10.*" />
    <PackageReference Include="System.ServiceModel.Security" Version="4.10.*" />
  </ItemGroup>
  
    
 
 <ItemGroup>
    <PackageReference Include="CoreWCF.Primitives" Version="1.*" />
    <PackageReference Include="CoreWCF.Http" Version="1.*" />
  </ItemGroup> 
 
Look inside the Reference.cs file, a method called ConfigureEndpoint is listed :
	
    
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.1.0")]
    public partial class ServiceClient : System.ServiceModel.ClientBase, MyService.IService
    {
        
        /// 
        /// Implement this partial method to configure the service endpoint.
        /// 
        /// The endpoint to configure
        /// The client credentials
        static partial void ConfigureEndpoint(System.ServiceModel.Description.ServiceEndpoint serviceEndpoint, System.ServiceModel.Description.ClientCredentials clientCredentials);

        //more code 
    
    
Next up, implementing this method to configured the binding.
	
    
namespace MyService
{
    public partial class ServiceClient
    {

        /// <summary>
        /// Implement this partial method to configure the service endpoint.
        /// </summary>
        /// <param name="serviceEndpoint">The endpoint to configure</param>
        /// <param name="clientCredentials">The client credentials</param>
        static partial void ConfigureEndpoint(System.ServiceModel.Description.ServiceEndpoint serviceEndpoint, System.ServiceModel.Description.ClientCredentials clientCredentials)
        {
            serviceEndpoint.Binding.OpenTimeout 
                = serviceEndpoint.Binding.CloseTimeout
                = serviceEndpoint.Binding.ReceiveTimeout
                = serviceEndpoint.Binding.SendTimeout = TimeSpan.FromSeconds(15);
        }

    }
}
    
    
We also want to be able to configure the timeout here. Lets add the following nuget packages also to the client (I got a .NET 6 console app):
	
 <PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" />
 <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
    
We can also avoid hardcoding timeouts by adding appsettings.json to our project and set the file to copy to output folder. If you are inside a console project you can add json config file like this, preferably registering it in some shared setup in Program.cs, but I found it a bit challenging to consume it from a static method I ended up with this :
 

            /// 
            /// Implement this partial method to configure the service endpoint.
            /// 
            /// The endpoint to configure
            /// The client credentials
            static partial void ConfigureEndpoint(System.ServiceModel.Description.ServiceEndpoint serviceEndpoint, System.ServiceModel.Description.ClientCredentials clientCredentials)
            {
                var serviceProvider = new ServiceCollection()
                    .AddSingleton(_ =>
                        new ConfigurationBuilder()
                            .SetBasePath(Path.Combine(AppContext.BaseDirectory))
                            .AddJsonFile("appsettings.json", optional: true)
                            .Build())
                    .BuildServiceProvider();
    
                var config = serviceProvider.GetService();
    
                int timeoutInSeconds = int.Parse(config!["ServiceTimeoutInSeconds"]);
                serviceEndpoint.Binding.OpenTimeout
                    = serviceEndpoint.Binding.CloseTimeout
                    = serviceEndpoint.Binding.ReceiveTimeout
                    = serviceEndpoint.Binding.SendTimeout = TimeSpan.FromSeconds(timeoutInSeconds);
            }
                 
               
And we have our appsettings.json file :


    {
      "ServiceTimeoutInSeconds" :  9
    }
    

The CoreWCF project got an upgrade tool that will do a lot of the migration for you. WCF had a lot of config settings and having an appsettings.json file for every setting will be some work. The upgrade tool should take care of generating some of these config values and add them into dedicated json files for this.

Monday 20 November 2023

Using synthesized speech in Azure Cognitive Services - Text to Speech

I have extended my demo repo with Multi-Lingual translator to include AI realistic speech. The Github repo for the demo is available here :

https://github.com/toreaurstadboss/MultiLingual.Translator

The speech synthesis service of Azure AI is accessed via a REST service. You can actually test it out first in Postman, retrieving an access token via an endpoint for this and then calling the text to speech endpoint using the access token as a bearer token. To get the demo working, you have to inside the Azure Portal create the necessary resources / services. This article is focused on speech service. Important, if you want to test out the DEMO yourself, remember to put the keys into environment variables so they are not exposed via source control. To get started with speech synthesis in Azure Cognitive Services, add a Speech Service resource via the Azure Portal. https://learn.microsoft.com/en-us/azure/ai-services/speech-service/overview We also need to add audio capability to our demo, which is a .NET MAUI Blazor app. The Nuget package used is the following : MultiLingual.Translator.csproj

<ItemGroup>
	<PackageReference Include="Plugin.Maui.Audio" Version="2.0.0" />
</ItemGroup>

This Nuget package's website is here: https://github.com/jfversluis/Plugin.Maui.Audio The MauiProgram.cs looks like the following, make note of AudioManager.Current, which is registered as a singleton. MauiProgram.cs


using Microsoft.Extensions.Configuration;
using MultiLingual.Translator.Lib;
using Plugin.Maui.Audio;

namespace MultiLingual.Translator;

public static class MauiProgram
{
	public static MauiApp CreateMauiApp()
	{
		var builder = MauiApp.CreateBuilder();
		builder
			.UseMauiApp<App>()
			.ConfigureFonts(fonts =>
			{
				fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
			});

		builder.Services.AddMauiBlazorWebView();
		#if DEBUG
		builder.Services.AddBlazorWebViewDeveloperTools();
#endif

		builder.Services.AddSingleton(AudioManager.Current);
		builder.Services.AddTransient<MainPage>();

		builder.Services.AddScoped<IDetectLanguageUtil, DetectLanguageUtil>();
        builder.Services.AddScoped<ITranslateUtil, TranslateUtil>();
		builder.Services.AddScoped<ITextToSpeechUtil, TextToSpeechUtil>();

		var config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
		builder.Configuration.AddConfiguration(config);

        return builder.Build();
	}
}



Next up, let's look at the TextToSpeechUtil. This class, which is a service that does two things against the REST API of the text-to-speech Azure Cognitive AI service :
  1. Fetch an access token
  2. Synthesize text to speech
TextToSpeechUtil.cs

using Microsoft.Extensions.Configuration;
using MultiLingual.Translator.Lib.Models;
using System.Security;
using System.Text;

namespace MultiLingual.Translator.Lib
{
    public class TextToSpeechUtil : ITextToSpeechUtil
    {

        public TextToSpeechUtil(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        public async Task<TextToSpeechResult> GetSpeechFromText(string text, string language, TextToSpeechLanguage[] actorVoices, string? preferredVoiceActorId)
        {
            var result = new TextToSpeechResult();

            result.Transcript = GetSpeechTextXml(text, language, actorVoices, preferredVoiceActorId, result);
            result.ContentType = _configuration[TextToSpeechSpeechContentType];
            result.OutputFormat = _configuration[TextToSpeechSpeechXMicrosoftOutputFormat];
            result.UserAgent = _configuration[TextToSpeechSpeechUserAgent];
            result.AvailableVoiceActorIds = ResolveAvailableActorVoiceIds(language, actorVoices);
            result.LanguageCode = language;

            string? token = await GetUpdatedToken();

            HttpClient httpClient = GetTextToSpeechWebClient(token);

            string ttsEndpointUrl = _configuration[TextToSpeechSpeechEndpoint];
            var response = await httpClient.PostAsync(ttsEndpointUrl, new StringContent(result.Transcript, Encoding.UTF8, result.ContentType));

            using (var memStream = new MemoryStream()) {
                var responseStream = await response.Content.ReadAsStreamAsync();
                responseStream.CopyTo(memStream);
                result.VoiceData = memStream.ToArray();
            }

            return result;
        }

        private async Task<string?> GetUpdatedToken()
        {
            string? token = _token?.ToNormalString();
            if (_lastTimeTokenFetched == null || DateTime.Now.Subtract(_lastTimeTokenFetched.Value).Minutes > 8)
            {
                token = await GetIssuedToken();
            }

            return token;
        }

        private HttpClient GetTextToSpeechWebClient(string? token)
        {
            var httpClient = new HttpClient();
            httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
            httpClient.DefaultRequestHeaders.Add("X-Microsoft-OutputFormat", _configuration[TextToSpeechSpeechXMicrosoftOutputFormat]);
            httpClient.DefaultRequestHeaders.Add("User-Agent", _configuration[TextToSpeechSpeechUserAgent]);
            return httpClient;
        }
       
        private string GetSpeechTextXml(string text, string language, TextToSpeechLanguage[] actorVoices, string? preferredVoiceActorId, TextToSpeechResult result)
        {
            result.VoiceActorId = ResolveVoiceActorId(language, preferredVoiceActorId, actorVoices);
            string speechXml = $@"
            <speak version='1.0' xml:lang='en-US'>
                <voice xml:lang='en-US' xml:gender='Male' name='Microsoft Server Speech Text to Speech Voice {result.VoiceActorId}'>
                    <prosody rate='1'>{text}</prosody>
                </voice>
            </speak>";
            return speechXml;               
        }

        private List<string> ResolveAvailableActorVoiceIds(string language, TextToSpeechLanguage[] actorVoices)
        {
            if (actorVoices?.Any() == true)
            {
                var voiceActorIds = actorVoices.Where(v => v.LanguageKey == language || v.LanguageKey.Split("-")[0] == language).SelectMany(v => v.VoiceActors).Select(v => v.VoiceId).ToList();
                return voiceActorIds;
            }
            return new List<string>();
        }

        private string ResolveVoiceActorId(string language, string? preferredVoiceActorId, TextToSpeechLanguage[] actorVoices)
        {
            string actorVoiceId = "(en-AU, NatashaNeural)"; //default to a select voice actor id 
            if (actorVoices?.Any() == true)
            {
                var voiceActorsForLanguage = actorVoices.Where(v => v.LanguageKey == language || v.LanguageKey.Split("-")[0] == language).SelectMany(v => v.VoiceActors).Select(v => v.VoiceId).ToList();
                if (voiceActorsForLanguage != null)
                {
                    if (voiceActorsForLanguage.Any() == true)
                    {
                        var resolvedPreferredVoiceActorId = voiceActorsForLanguage.FirstOrDefault(v => v == preferredVoiceActorId);
                        if (!string.IsNullOrWhiteSpace(resolvedPreferredVoiceActorId))
                        {
                            return resolvedPreferredVoiceActorId!;
                        }
                        actorVoiceId = voiceActorsForLanguage.First();
                    }
                }
            }
            return actorVoiceId;
        }

        private async Task<string> GetIssuedToken()
        {
            var httpClient = new HttpClient();
            string? textToSpeechSubscriptionKey = Environment.GetEnvironmentVariable("AZURE_TEXT_SPEECH_SUBSCRIPTION_KEY", EnvironmentVariableTarget.Machine);
            httpClient.DefaultRequestHeaders.Add(OcpApiSubscriptionKeyHeaderName, textToSpeechSubscriptionKey);
            string tokenEndpointUrl = _configuration[TextToSpeechIssueTokenEndpoint];
            var response = await httpClient.PostAsync(tokenEndpointUrl, new StringContent("{}"));
            _token = (await response.Content.ReadAsStringAsync()).ToSecureString();
            _lastTimeTokenFetched = DateTime.Now;
            return _token.ToNormalString();
        }

        private const string OcpApiSubscriptionKeyHeaderName = "Ocp-Apim-Subscription-Key";
        private const string TextToSpeechIssueTokenEndpoint = "TextToSpeechIssueTokenEndpoint";
        private const string TextToSpeechSpeechEndpoint = "TextToSpeechSpeechEndpoint";        
        private const string TextToSpeechSpeechContentType = "TextToSpeechSpeechContentType";
        private const string TextToSpeechSpeechUserAgent = "TextToSpeechSpeechUserAgent";
        private const string TextToSpeechSpeechXMicrosoftOutputFormat = "TextToSpeechSpeechXMicrosoftOutputFormat";

        private readonly IConfiguration _configuration;

        private DateTime? _lastTimeTokenFetched = null;
        private SecureString _token = null;

    }
}



Let's look at the appsettings.json file. The Ocp-Apim-Subscription-Key is put into environment variable, this is a secret key you do not want to expose to avoid leaking a key an running costs for usage of service. Appsettings.json


{
  "TextToSpeechIssueTokenEndpoint": "https://norwayeast.api.cognitive.microsoft.com/sts/v1.0/issuetoken",
  "TextToSpeechSpeechEndpoint": "https://norwayeast.tts.speech.microsoft.com/cognitiveservices/v1",
  "TextToSpeechSpeechContentType": "application/ssml+xml",
  "TextToSpeechSpeechUserAgent": "MultiLingualTranslatorBlazorDemo",
  "TextToSpeechSpeechXMicrosoftOutputFormat": "audio-24khz-48kbitrate-mono-mp3"
}




Next up, I have gathered all the voice actor ids for languages in Azure Cognitive Services which have voice actor ids. Thesee are all the most known languages in the list of Azure about 150 supported languages, see the following json for an overview of voice actor ids. For example, Norwegian language got three voice actors that are synthesized neural net trained AI voice actors for realistic speech synthesis.

       [
  {
    "LanguageKey": "af-ZA",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "af-ZA-AdriNeural2",
        "VoiceId": "(af-ZA, AdriNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "af-ZA-WillemNeural2",
        "VoiceId": "(af-ZA, WillemNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "am-ET",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "am-ET-MekdesNeural2",
        "VoiceId": "(am-ET, MekdesNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "am-ET-AmehaNeural2",
        "VoiceId": "(am-ET, AmehaNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "ar-AE",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ar-AE-FatimaNeural",
        "VoiceId": "(ar-AE, FatimaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ar-AE-HamdanNeural",
        "VoiceId": "(ar-AE, HamdanNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ar-BH",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ar-BH-LailaNeural",
        "VoiceId": "(ar-BH, LailaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ar-BH-AliNeural",
        "VoiceId": "(ar-BH, AliNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ar-DZ",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ar-DZ-AminaNeural",
        "VoiceId": "(ar-DZ, AminaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ar-DZ-IsmaelNeural",
        "VoiceId": "(ar-DZ, IsmaelNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ar-EG",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ar-EG-SalmaNeural",
        "VoiceId": "(ar-EG, SalmaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ar-EG-ShakirNeural",
        "VoiceId": "(ar-EG, ShakirNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ar-IQ",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ar-IQ-RanaNeural",
        "VoiceId": "(ar-IQ, RanaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ar-IQ-BasselNeural",
        "VoiceId": "(ar-IQ, BasselNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ar-JO",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ar-JO-SanaNeural",
        "VoiceId": "(ar-JO, SanaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ar-JO-TaimNeural",
        "VoiceId": "(ar-JO, TaimNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ar-KW",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ar-KW-NouraNeural",
        "VoiceId": "(ar-KW, NouraNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ar-KW-FahedNeural",
        "VoiceId": "(ar-KW, FahedNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ar-LB",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ar-LB-LaylaNeural",
        "VoiceId": "(ar-LB, LaylaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ar-LB-RamiNeural",
        "VoiceId": "(ar-LB, RamiNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ar-LY",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ar-LY-ImanNeural",
        "VoiceId": "(ar-LY, ImanNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ar-LY-OmarNeural",
        "VoiceId": "(ar-LY, OmarNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ar-MA",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ar-MA-MounaNeural",
        "VoiceId": "(ar-MA, MounaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ar-MA-JamalNeural",
        "VoiceId": "(ar-MA, JamalNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ar-OM",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ar-OM-AyshaNeural",
        "VoiceId": "(ar-OM, AyshaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ar-OM-AbdullahNeural",
        "VoiceId": "(ar-OM, AbdullahNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ar-QA",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ar-QA-AmalNeural",
        "VoiceId": "(ar-QA, AmalNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ar-QA-MoazNeural",
        "VoiceId": "(ar-QA, MoazNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ar-SA",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ar-SA-ZariyahNeural",
        "VoiceId": "(ar-SA, ZariyahNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ar-SA-HamedNeural",
        "VoiceId": "(ar-SA, HamedNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ar-SY",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ar-SY-AmanyNeural",
        "VoiceId": "(ar-SY, AmanyNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ar-SY-LaithNeural",
        "VoiceId": "(ar-SY, LaithNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ar-TN",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ar-TN-ReemNeural",
        "VoiceId": "(ar-TN, ReemNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ar-TN-HediNeural",
        "VoiceId": "(ar-TN, HediNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ar-YE",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ar-YE-MaryamNeural",
        "VoiceId": "(ar-YE, MaryamNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ar-YE-SalehNeural",
        "VoiceId": "(ar-YE, SalehNeural)"
      }
    ]
  },
  {
    "LanguageKey": "az-AZ",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "az-AZ-BanuNeural2",
        "VoiceId": "(az-AZ, BanuNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "az-AZ-BabekNeural2",
        "VoiceId": "(az-AZ, BabekNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "bg-BG",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "bg-BG-KalinaNeural",
        "VoiceId": "(bg-BG, KalinaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "bg-BG-BorislavNeural",
        "VoiceId": "(bg-BG, BorislavNeural)"
      }
    ]
  },
  {
    "LanguageKey": "bn-BD",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "bn-BD-NabanitaNeural2",
        "VoiceId": "(bn-BD, NabanitaNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "bn-BD-PradeepNeural2",
        "VoiceId": "(bn-BD, PradeepNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "bn-IN",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "bn-IN-TanishaaNeural2",
        "VoiceId": "(bn-IN, TanishaaNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "bn-IN-BashkarNeural2",
        "VoiceId": "(bn-IN, BashkarNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "bs-BA",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "bs-BA-VesnaNeural2",
        "VoiceId": "(bs-BA, VesnaNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "bs-BA-GoranNeural2",
        "VoiceId": "(bs-BA, GoranNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "ca-ES",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ca-ES-JoanaNeural",
        "VoiceId": "(ca-ES, JoanaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ca-ES-EnricNeural",
        "VoiceId": "(ca-ES, EnricNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "ca-ES-AlbaNeural",
        "VoiceId": "(ca-ES, AlbaNeural)"
      }
    ]
  },
  {
    "LanguageKey": "cs-CZ",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "cs-CZ-VlastaNeural",
        "VoiceId": "(cs-CZ, VlastaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "cs-CZ-AntoninNeural",
        "VoiceId": "(cs-CZ, AntoninNeural)"
      }
    ]
  },
  {
    "LanguageKey": "cy-GB",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "cy-GB-NiaNeural2",
        "VoiceId": "(cy-GB, NiaNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "cy-GB-AledNeural2",
        "VoiceId": "(cy-GB, AledNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "da-DK",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "da-DK-ChristelNeural",
        "VoiceId": "(da-DK, ChristelNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "da-DK-JeppeNeural",
        "VoiceId": "(da-DK, JeppeNeural)"
      }
    ]
  },
  {
    "LanguageKey": "de-AT",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "de-AT-IngridNeural",
        "VoiceId": "(de-AT, IngridNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "de-AT-JonasNeural",
        "VoiceId": "(de-AT, JonasNeural)"
      }
    ]
  },
  {
    "LanguageKey": "de-CH",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "de-CH-LeniNeural",
        "VoiceId": "(de-CH, LeniNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "de-CH-JanNeural",
        "VoiceId": "(de-CH, JanNeural)"
      }
    ]
  },
  {
    "LanguageKey": "de-DE",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "de-DE-KatjaNeural",
        "VoiceId": "(de-DE, KatjaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "de-DE-ConradNeural1",
        "VoiceId": "(de-DE, ConradNeural1)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "de-DE-AmalaNeural",
        "VoiceId": "(de-DE, AmalaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "de-DE-BerndNeural",
        "VoiceId": "(de-DE, BerndNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "de-DE-ChristophNeural",
        "VoiceId": "(de-DE, ChristophNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "de-DE-ElkeNeural",
        "VoiceId": "(de-DE, ElkeNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "de-DE-GiselaNeural",
        "VoiceId": "(de-DE, GiselaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "de-DE-KasperNeural",
        "VoiceId": "(de-DE, KasperNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "de-DE-KillianNeural",
        "VoiceId": "(de-DE, KillianNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "de-DE-KlarissaNeural",
        "VoiceId": "(de-DE, KlarissaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "de-DE-KlausNeural",
        "VoiceId": "(de-DE, KlausNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "de-DE-LouisaNeural",
        "VoiceId": "(de-DE, LouisaNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "de-DE-MajaNeural",
        "VoiceId": "(de-DE, MajaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "de-DE-RalfNeural",
        "VoiceId": "(de-DE, RalfNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "de-DE-SeraphinaNeural",
        "VoiceId": "(de-DE, SeraphinaNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "de-DE-TanjaNeural",
        "VoiceId": "(de-DE, TanjaNeural)"
      }
    ]
  },
  {
    "LanguageKey": "el-GR",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "el-GR-AthinaNeural",
        "VoiceId": "(el-GR, AthinaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "el-GR-NestorasNeural",
        "VoiceId": "(el-GR, NestorasNeural)"
      }
    ]
  },
  {
    "LanguageKey": "en-AU",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "en-AU-NatashaNeural",
        "VoiceId": "(en-AU, NatashaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-AU-WilliamNeural",
        "VoiceId": "(en-AU, WilliamNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-AU-AnnetteNeural",
        "VoiceId": "(en-AU, AnnetteNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-AU-CarlyNeural",
        "VoiceId": "(en-AU, CarlyNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-AU-DarrenNeural",
        "VoiceId": "(en-AU, DarrenNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-AU-DuncanNeural",
        "VoiceId": "(en-AU, DuncanNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-AU-ElsieNeural",
        "VoiceId": "(en-AU, ElsieNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-AU-FreyaNeural",
        "VoiceId": "(en-AU, FreyaNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-AU-JoanneNeural",
        "VoiceId": "(en-AU, JoanneNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-AU-KenNeural",
        "VoiceId": "(en-AU, KenNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-AU-KimNeural",
        "VoiceId": "(en-AU, KimNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-AU-NeilNeural",
        "VoiceId": "(en-AU, NeilNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-AU-TimNeural",
        "VoiceId": "(en-AU, TimNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-AU-TinaNeural",
        "VoiceId": "(en-AU, TinaNeural)"
      }
    ]
  },
  {
    "LanguageKey": "en-CA",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "en-CA-ClaraNeural",
        "VoiceId": "(en-CA, ClaraNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-CA-LiamNeural",
        "VoiceId": "(en-CA, LiamNeural)"
      }
    ]
  },
  {
    "LanguageKey": "en-GB",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "en-GB-SoniaNeural",
        "VoiceId": "(en-GB, SoniaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-GB-RyanNeural",
        "VoiceId": "(en-GB, RyanNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-GB-LibbyNeural",
        "VoiceId": "(en-GB, LibbyNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-GB-AbbiNeural",
        "VoiceId": "(en-GB, AbbiNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-GB-AlfieNeural",
        "VoiceId": "(en-GB, AlfieNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-GB-BellaNeural",
        "VoiceId": "(en-GB, BellaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-GB-ElliotNeural",
        "VoiceId": "(en-GB, ElliotNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-GB-EthanNeural",
        "VoiceId": "(en-GB, EthanNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-GB-HollieNeural",
        "VoiceId": "(en-GB, HollieNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-GB-MaisieNeural",
        "VoiceId": "(en-GB, MaisieNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-GB-NoahNeural",
        "VoiceId": "(en-GB, NoahNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-GB-OliverNeural",
        "VoiceId": "(en-GB, OliverNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-GB-OliviaNeural",
        "VoiceId": "(en-GB, OliviaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-GB-ThomasNeural",
        "VoiceId": "(en-GB, ThomasNeural)"
      }
    ]
  },
  {
    "LanguageKey": "en-HK",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "en-HK-YanNeural",
        "VoiceId": "(en-HK, YanNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-HK-SamNeural",
        "VoiceId": "(en-HK, SamNeural)"
      }
    ]
  },
  {
    "LanguageKey": "en-IE",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "en-IE-EmilyNeural",
        "VoiceId": "(en-IE, EmilyNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-IE-ConnorNeural",
        "VoiceId": "(en-IE, ConnorNeural)"
      }
    ]
  },
  {
    "LanguageKey": "en-IN",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "en-IN-NeerjaNeural",
        "VoiceId": "(en-IN, NeerjaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-IN-PrabhatNeural",
        "VoiceId": "(en-IN, PrabhatNeural)"
      }
    ]
  },
  {
    "LanguageKey": "en-KE",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "en-KE-AsiliaNeural",
        "VoiceId": "(en-KE, AsiliaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-KE-ChilembaNeural",
        "VoiceId": "(en-KE, ChilembaNeural)"
      }
    ]
  },
  {
    "LanguageKey": "en-NG",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "en-NG-EzinneNeural",
        "VoiceId": "(en-NG, EzinneNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-NG-AbeoNeural",
        "VoiceId": "(en-NG, AbeoNeural)"
      }
    ]
  },
  {
    "LanguageKey": "en-NZ",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "en-NZ-MollyNeural",
        "VoiceId": "(en-NZ, MollyNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-NZ-MitchellNeural",
        "VoiceId": "(en-NZ, MitchellNeural)"
      }
    ]
  },
  {
    "LanguageKey": "en-PH",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "en-PH-RosaNeural",
        "VoiceId": "(en-PH, RosaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-PH-JamesNeural",
        "VoiceId": "(en-PH, JamesNeural)"
      }
    ]
  },
  {
    "LanguageKey": "en-SG",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "en-SG-LunaNeural",
        "VoiceId": "(en-SG, LunaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-SG-WayneNeural",
        "VoiceId": "(en-SG, WayneNeural)"
      }
    ]
  },
  {
    "LanguageKey": "en-TZ",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "en-TZ-ImaniNeural",
        "VoiceId": "(en-TZ, ImaniNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-TZ-ElimuNeural",
        "VoiceId": "(en-TZ, ElimuNeural)"
      }
    ]
  },
  {
    "LanguageKey": "en-US",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "en-US-JennyMultilingualNeural3",
        "VoiceId": "(en-US, JennyMultilingualNeural3)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-US-JennyNeural",
        "VoiceId": "(en-US, JennyNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-US-GuyNeural",
        "VoiceId": "(en-US, GuyNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-US-AriaNeural",
        "VoiceId": "(en-US, AriaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-US-DavisNeural",
        "VoiceId": "(en-US, DavisNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-US-AmberNeural",
        "VoiceId": "(en-US, AmberNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-US-AnaNeural",
        "VoiceId": "(en-US, AnaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-US-AndrewNeural",
        "VoiceId": "(en-US, AndrewNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-US-AshleyNeural",
        "VoiceId": "(en-US, AshleyNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-US-BrandonNeural",
        "VoiceId": "(en-US, BrandonNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-US-BrianNeural",
        "VoiceId": "(en-US, BrianNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-US-ChristopherNeural",
        "VoiceId": "(en-US, ChristopherNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-US-CoraNeural",
        "VoiceId": "(en-US, CoraNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-US-ElizabethNeural",
        "VoiceId": "(en-US, ElizabethNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-US-EmmaNeural",
        "VoiceId": "(en-US, EmmaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-US-EricNeural",
        "VoiceId": "(en-US, EricNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-US-JacobNeural",
        "VoiceId": "(en-US, JacobNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-US-JaneNeural",
        "VoiceId": "(en-US, JaneNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-US-JasonNeural",
        "VoiceId": "(en-US, JasonNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-US-MichelleNeural",
        "VoiceId": "(en-US, MichelleNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-US-MonicaNeural",
        "VoiceId": "(en-US, MonicaNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-US-NancyNeural",
        "VoiceId": "(en-US, NancyNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-US-RogerNeural",
        "VoiceId": "(en-US, RogerNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-US-SaraNeural",
        "VoiceId": "(en-US, SaraNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-US-SteffanNeural",
        "VoiceId": "(en-US, SteffanNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-US-TonyNeural",
        "VoiceId": "(en-US, TonyNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-US-AIGenerate1Neural1",
        "VoiceId": "(en-US, AIGenerate1Neural1)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-US-AIGenerate2Neural1",
        "VoiceId": "(en-US, AIGenerate2Neural1)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-US-BlueNeural1",
        "VoiceId": "(en-US, BlueNeural1)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "en-US-JennyMultilingualV2Neural1,3",
        "VoiceId": "(en-US, JennyMultilingualV2Neural1,3)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-US-RyanMultilingualNeural1,3",
        "VoiceId": "(en-US, RyanMultilingualNeural1,3)"
      }
    ]
  },
  {
    "LanguageKey": "en-ZA",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "en-ZA-LeahNeural",
        "VoiceId": "(en-ZA, LeahNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "en-ZA-LukeNeural",
        "VoiceId": "(en-ZA, LukeNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-AR",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-AR-ElenaNeural",
        "VoiceId": "(es-AR, ElenaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-AR-TomasNeural",
        "VoiceId": "(es-AR, TomasNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-BO",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-BO-SofiaNeural",
        "VoiceId": "(es-BO, SofiaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-BO-MarceloNeural",
        "VoiceId": "(es-BO, MarceloNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-CL",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-CL-CatalinaNeural",
        "VoiceId": "(es-CL, CatalinaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-CL-LorenzoNeural",
        "VoiceId": "(es-CL, LorenzoNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-CO",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-CO-SalomeNeural",
        "VoiceId": "(es-CO, SalomeNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-CO-GonzaloNeural",
        "VoiceId": "(es-CO, GonzaloNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-CR",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-CR-MariaNeural",
        "VoiceId": "(es-CR, MariaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-CR-JuanNeural",
        "VoiceId": "(es-CR, JuanNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-CU",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-CU-BelkysNeural",
        "VoiceId": "(es-CU, BelkysNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-CU-ManuelNeural",
        "VoiceId": "(es-CU, ManuelNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-DO",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-DO-RamonaNeural",
        "VoiceId": "(es-DO, RamonaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-DO-EmilioNeural",
        "VoiceId": "(es-DO, EmilioNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-EC",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-EC-AndreaNeural",
        "VoiceId": "(es-EC, AndreaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-EC-LuisNeural",
        "VoiceId": "(es-EC, LuisNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-ES",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-ES-ElviraNeural",
        "VoiceId": "(es-ES, ElviraNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-ES-AlvaroNeural",
        "VoiceId": "(es-ES, AlvaroNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "es-ES-AbrilNeural",
        "VoiceId": "(es-ES, AbrilNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-ES-ArnauNeural",
        "VoiceId": "(es-ES, ArnauNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-ES-DarioNeural",
        "VoiceId": "(es-ES, DarioNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-ES-EliasNeural",
        "VoiceId": "(es-ES, EliasNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "es-ES-EstrellaNeural",
        "VoiceId": "(es-ES, EstrellaNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "es-ES-IreneNeural",
        "VoiceId": "(es-ES, IreneNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "es-ES-LaiaNeural",
        "VoiceId": "(es-ES, LaiaNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "es-ES-LiaNeural",
        "VoiceId": "(es-ES, LiaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-ES-NilNeural",
        "VoiceId": "(es-ES, NilNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-ES-SaulNeural",
        "VoiceId": "(es-ES, SaulNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-ES-TeoNeural",
        "VoiceId": "(es-ES, TeoNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "es-ES-TrianaNeural",
        "VoiceId": "(es-ES, TrianaNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "es-ES-VeraNeural",
        "VoiceId": "(es-ES, VeraNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "es-ES-XimenaNeural",
        "VoiceId": "(es-ES, XimenaNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-GQ",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-GQ-TeresaNeural",
        "VoiceId": "(es-GQ, TeresaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-GQ-JavierNeural",
        "VoiceId": "(es-GQ, JavierNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-GT",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-GT-MartaNeural",
        "VoiceId": "(es-GT, MartaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-GT-AndresNeural",
        "VoiceId": "(es-GT, AndresNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-HN",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-HN-KarlaNeural",
        "VoiceId": "(es-HN, KarlaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-HN-CarlosNeural",
        "VoiceId": "(es-HN, CarlosNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-MX",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-MX-DaliaNeural",
        "VoiceId": "(es-MX, DaliaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-MX-JorgeNeural",
        "VoiceId": "(es-MX, JorgeNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "es-MX-BeatrizNeural",
        "VoiceId": "(es-MX, BeatrizNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "es-MX-CandelaNeural",
        "VoiceId": "(es-MX, CandelaNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "es-MX-CarlotaNeural",
        "VoiceId": "(es-MX, CarlotaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-MX-CecilioNeural",
        "VoiceId": "(es-MX, CecilioNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-MX-GerardoNeural",
        "VoiceId": "(es-MX, GerardoNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "es-MX-LarissaNeural",
        "VoiceId": "(es-MX, LarissaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-MX-LibertoNeural",
        "VoiceId": "(es-MX, LibertoNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-MX-LucianoNeural",
        "VoiceId": "(es-MX, LucianoNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "es-MX-MarinaNeural",
        "VoiceId": "(es-MX, MarinaNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "es-MX-NuriaNeural",
        "VoiceId": "(es-MX, NuriaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-MX-PelayoNeural",
        "VoiceId": "(es-MX, PelayoNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "es-MX-RenataNeural",
        "VoiceId": "(es-MX, RenataNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-MX-YagoNeural",
        "VoiceId": "(es-MX, YagoNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-NI",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-NI-YolandaNeural",
        "VoiceId": "(es-NI, YolandaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-NI-FedericoNeural",
        "VoiceId": "(es-NI, FedericoNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-PA",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-PA-MargaritaNeural",
        "VoiceId": "(es-PA, MargaritaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-PA-RobertoNeural",
        "VoiceId": "(es-PA, RobertoNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-PE",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-PE-CamilaNeural",
        "VoiceId": "(es-PE, CamilaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-PE-AlexNeural",
        "VoiceId": "(es-PE, AlexNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-PR",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-PR-KarinaNeural",
        "VoiceId": "(es-PR, KarinaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-PR-VictorNeural",
        "VoiceId": "(es-PR, VictorNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-PY",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-PY-TaniaNeural",
        "VoiceId": "(es-PY, TaniaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-PY-MarioNeural",
        "VoiceId": "(es-PY, MarioNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-SV",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-SV-LorenaNeural",
        "VoiceId": "(es-SV, LorenaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-SV-RodrigoNeural",
        "VoiceId": "(es-SV, RodrigoNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-US",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-US-PalomaNeural",
        "VoiceId": "(es-US, PalomaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-US-AlonsoNeural",
        "VoiceId": "(es-US, AlonsoNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-UY",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-UY-ValentinaNeural",
        "VoiceId": "(es-UY, ValentinaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-UY-MateoNeural",
        "VoiceId": "(es-UY, MateoNeural)"
      }
    ]
  },
  {
    "LanguageKey": "es-VE",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "es-VE-PaolaNeural",
        "VoiceId": "(es-VE, PaolaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "es-VE-SebastianNeural",
        "VoiceId": "(es-VE, SebastianNeural)"
      }
    ]
  },
  {
    "LanguageKey": "et-EE",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "et-EE-AnuNeural2",
        "VoiceId": "(et-EE, AnuNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "et-EE-KertNeural2",
        "VoiceId": "(et-EE, KertNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "eu-ES",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "eu-ES-AinhoaNeural2",
        "VoiceId": "(eu-ES, AinhoaNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "eu-ES-AnderNeural2",
        "VoiceId": "(eu-ES, AnderNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "fa-IR",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "fa-IR-DilaraNeural2",
        "VoiceId": "(fa-IR, DilaraNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "fa-IR-FaridNeural2",
        "VoiceId": "(fa-IR, FaridNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "fi-FI",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "fi-FI-SelmaNeural",
        "VoiceId": "(fi-FI, SelmaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "fi-FI-HarriNeural",
        "VoiceId": "(fi-FI, HarriNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "fi-FI-NooraNeural",
        "VoiceId": "(fi-FI, NooraNeural)"
      }
    ]
  },
  {
    "LanguageKey": "fil-PH",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "fil-PH-BlessicaNeural2",
        "VoiceId": "(fil-PH, BlessicaNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "fil-PH-AngeloNeural2",
        "VoiceId": "(fil-PH, AngeloNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "fr-BE",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "fr-BE-CharlineNeural",
        "VoiceId": "(fr-BE, CharlineNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "fr-BE-GerardNeural",
        "VoiceId": "(fr-BE, GerardNeural)"
      }
    ]
  },
  {
    "LanguageKey": "fr-CA",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "fr-CA-SylvieNeural",
        "VoiceId": "(fr-CA, SylvieNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "fr-CA-JeanNeural",
        "VoiceId": "(fr-CA, JeanNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "fr-CA-AntoineNeural",
        "VoiceId": "(fr-CA, AntoineNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "fr-CA-ThierryNeural",
        "VoiceId": "(fr-CA, ThierryNeural)"
      }
    ]
  },
  {
    "LanguageKey": "fr-CH",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "fr-CH-ArianeNeural",
        "VoiceId": "(fr-CH, ArianeNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "fr-CH-FabriceNeural",
        "VoiceId": "(fr-CH, FabriceNeural)"
      }
    ]
  },
  {
    "LanguageKey": "fr-FR",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "fr-FR-DeniseNeural",
        "VoiceId": "(fr-FR, DeniseNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "fr-FR-HenriNeural",
        "VoiceId": "(fr-FR, HenriNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "fr-FR-AlainNeural",
        "VoiceId": "(fr-FR, AlainNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "fr-FR-BrigitteNeural",
        "VoiceId": "(fr-FR, BrigitteNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "fr-FR-CelesteNeural",
        "VoiceId": "(fr-FR, CelesteNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "fr-FR-ClaudeNeural",
        "VoiceId": "(fr-FR, ClaudeNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "fr-FR-CoralieNeural",
        "VoiceId": "(fr-FR, CoralieNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "fr-FR-EloiseNeural",
        "VoiceId": "(fr-FR, EloiseNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "fr-FR-JacquelineNeural",
        "VoiceId": "(fr-FR, JacquelineNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "fr-FR-JeromeNeural",
        "VoiceId": "(fr-FR, JeromeNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "fr-FR-JosephineNeural",
        "VoiceId": "(fr-FR, JosephineNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "fr-FR-MauriceNeural",
        "VoiceId": "(fr-FR, MauriceNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "fr-FR-VivienneNeural",
        "VoiceId": "(fr-FR, VivienneNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "fr-FR-YvesNeural",
        "VoiceId": "(fr-FR, YvesNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "fr-FR-YvetteNeural",
        "VoiceId": "(fr-FR, YvetteNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ga-IE",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ga-IE-OrlaNeural2",
        "VoiceId": "(ga-IE, OrlaNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ga-IE-ColmNeural2",
        "VoiceId": "(ga-IE, ColmNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "gl-ES",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "gl-ES-SabelaNeural2",
        "VoiceId": "(gl-ES, SabelaNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "gl-ES-RoiNeural2",
        "VoiceId": "(gl-ES, RoiNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "gu-IN",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "gu-IN-DhwaniNeural",
        "VoiceId": "(gu-IN, DhwaniNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "gu-IN-NiranjanNeural",
        "VoiceId": "(gu-IN, NiranjanNeural)"
      }
    ]
  },
  {
    "LanguageKey": "he-IL",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "he-IL-HilaNeural",
        "VoiceId": "(he-IL, HilaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "he-IL-AvriNeural",
        "VoiceId": "(he-IL, AvriNeural)"
      }
    ]
  },
  {
    "LanguageKey": "hi-IN",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "hi-IN-SwaraNeural",
        "VoiceId": "(hi-IN, SwaraNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "hi-IN-MadhurNeural",
        "VoiceId": "(hi-IN, MadhurNeural)"
      }
    ]
  },
  {
    "LanguageKey": "hr-HR",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "hr-HR-GabrijelaNeural",
        "VoiceId": "(hr-HR, GabrijelaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "hr-HR-SreckoNeural",
        "VoiceId": "(hr-HR, SreckoNeural)"
      }
    ]
  },
  {
    "LanguageKey": "hu-HU",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "hu-HU-NoemiNeural",
        "VoiceId": "(hu-HU, NoemiNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "hu-HU-TamasNeural",
        "VoiceId": "(hu-HU, TamasNeural)"
      }
    ]
  },
  {
    "LanguageKey": "hy-AM",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "hy-AM-AnahitNeural2",
        "VoiceId": "(hy-AM, AnahitNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "hy-AM-HaykNeural2",
        "VoiceId": "(hy-AM, HaykNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "id-ID",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "id-ID-GadisNeural",
        "VoiceId": "(id-ID, GadisNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "id-ID-ArdiNeural",
        "VoiceId": "(id-ID, ArdiNeural)"
      }
    ]
  },
  {
    "LanguageKey": "is-IS",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "is-IS-GudrunNeural2",
        "VoiceId": "(is-IS, GudrunNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "is-IS-GunnarNeural2",
        "VoiceId": "(is-IS, GunnarNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "it-IT",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "it-IT-ElsaNeural",
        "VoiceId": "(it-IT, ElsaNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "it-IT-IsabellaNeural",
        "VoiceId": "(it-IT, IsabellaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "it-IT-DiegoNeural",
        "VoiceId": "(it-IT, DiegoNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "it-IT-BenignoNeural",
        "VoiceId": "(it-IT, BenignoNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "it-IT-CalimeroNeural",
        "VoiceId": "(it-IT, CalimeroNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "it-IT-CataldoNeural",
        "VoiceId": "(it-IT, CataldoNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "it-IT-FabiolaNeural",
        "VoiceId": "(it-IT, FabiolaNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "it-IT-FiammaNeural",
        "VoiceId": "(it-IT, FiammaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "it-IT-GianniNeural",
        "VoiceId": "(it-IT, GianniNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "it-IT-GiuseppeNeural",
        "VoiceId": "(it-IT, GiuseppeNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "it-IT-ImeldaNeural",
        "VoiceId": "(it-IT, ImeldaNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "it-IT-IrmaNeural",
        "VoiceId": "(it-IT, IrmaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "it-IT-LisandroNeural",
        "VoiceId": "(it-IT, LisandroNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "it-IT-PalmiraNeural",
        "VoiceId": "(it-IT, PalmiraNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "it-IT-PierinaNeural",
        "VoiceId": "(it-IT, PierinaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "it-IT-RinaldoNeural",
        "VoiceId": "(it-IT, RinaldoNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ja-JP",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ja-JP-NanamiNeural",
        "VoiceId": "(ja-JP, NanamiNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ja-JP-KeitaNeural",
        "VoiceId": "(ja-JP, KeitaNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "ja-JP-AoiNeural",
        "VoiceId": "(ja-JP, AoiNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ja-JP-DaichiNeural",
        "VoiceId": "(ja-JP, DaichiNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "ja-JP-MayuNeural",
        "VoiceId": "(ja-JP, MayuNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ja-JP-NaokiNeural",
        "VoiceId": "(ja-JP, NaokiNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "ja-JP-ShioriNeural",
        "VoiceId": "(ja-JP, ShioriNeural)"
      }
    ]
  },
  {
    "LanguageKey": "jv-ID",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "jv-ID-SitiNeural2",
        "VoiceId": "(jv-ID, SitiNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "jv-ID-DimasNeural2",
        "VoiceId": "(jv-ID, DimasNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "ka-GE",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ka-GE-EkaNeural2",
        "VoiceId": "(ka-GE, EkaNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ka-GE-GiorgiNeural2",
        "VoiceId": "(ka-GE, GiorgiNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "kk-KZ",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "kk-KZ-AigulNeural2",
        "VoiceId": "(kk-KZ, AigulNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "kk-KZ-DauletNeural2",
        "VoiceId": "(kk-KZ, DauletNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "km-KH",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "km-KH-SreymomNeural2",
        "VoiceId": "(km-KH, SreymomNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "km-KH-PisethNeural2",
        "VoiceId": "(km-KH, PisethNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "kn-IN",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "kn-IN-SapnaNeural2",
        "VoiceId": "(kn-IN, SapnaNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "kn-IN-GaganNeural2",
        "VoiceId": "(kn-IN, GaganNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "ko-KR",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ko-KR-SunHiNeural",
        "VoiceId": "(ko-KR, SunHiNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ko-KR-InJoonNeural",
        "VoiceId": "(ko-KR, InJoonNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ko-KR-BongJinNeural",
        "VoiceId": "(ko-KR, BongJinNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ko-KR-GookMinNeural",
        "VoiceId": "(ko-KR, GookMinNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ko-KR-HyunsuNeural",
        "VoiceId": "(ko-KR, HyunsuNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "ko-KR-JiMinNeural",
        "VoiceId": "(ko-KR, JiMinNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "ko-KR-SeoHyeonNeural",
        "VoiceId": "(ko-KR, SeoHyeonNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "ko-KR-SoonBokNeural",
        "VoiceId": "(ko-KR, SoonBokNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "ko-KR-YuJinNeural",
        "VoiceId": "(ko-KR, YuJinNeural)"
      }
    ]
  },
  {
    "LanguageKey": "lo-LA",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "lo-LA-KeomanyNeural2",
        "VoiceId": "(lo-LA, KeomanyNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "lo-LA-ChanthavongNeural2",
        "VoiceId": "(lo-LA, ChanthavongNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "lt-LT",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "lt-LT-OnaNeural2",
        "VoiceId": "(lt-LT, OnaNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "lt-LT-LeonasNeural2",
        "VoiceId": "(lt-LT, LeonasNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "lv-LV",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "lv-LV-EveritaNeural2",
        "VoiceId": "(lv-LV, EveritaNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "lv-LV-NilsNeural2",
        "VoiceId": "(lv-LV, NilsNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "mk-MK",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "mk-MK-MarijaNeural2",
        "VoiceId": "(mk-MK, MarijaNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "mk-MK-AleksandarNeural2",
        "VoiceId": "(mk-MK, AleksandarNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "ml-IN",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ml-IN-SobhanaNeural2",
        "VoiceId": "(ml-IN, SobhanaNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ml-IN-MidhunNeural2",
        "VoiceId": "(ml-IN, MidhunNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "mn-MN",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "mn-MN-YesuiNeural2",
        "VoiceId": "(mn-MN, YesuiNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "mn-MN-BataaNeural2",
        "VoiceId": "(mn-MN, BataaNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "mr-IN",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "mr-IN-AarohiNeural",
        "VoiceId": "(mr-IN, AarohiNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "mr-IN-ManoharNeural",
        "VoiceId": "(mr-IN, ManoharNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ms-MY",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ms-MY-YasminNeural",
        "VoiceId": "(ms-MY, YasminNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ms-MY-OsmanNeural",
        "VoiceId": "(ms-MY, OsmanNeural)"
      }
    ]
  },
  {
    "LanguageKey": "mt-MT",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "mt-MT-GraceNeural2",
        "VoiceId": "(mt-MT, GraceNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "mt-MT-JosephNeural2",
        "VoiceId": "(mt-MT, JosephNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "my-MM",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "my-MM-NilarNeural2",
        "VoiceId": "(my-MM, NilarNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "my-MM-ThihaNeural2",
        "VoiceId": "(my-MM, ThihaNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "nb-NO",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "nb-NO-PernilleNeural",
        "VoiceId": "(nb-NO, PernilleNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "nb-NO-FinnNeural",
        "VoiceId": "(nb-NO, FinnNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "nb-NO-IselinNeural",
        "VoiceId": "(nb-NO, IselinNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ne-NP",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ne-NP-HemkalaNeural2",
        "VoiceId": "(ne-NP, HemkalaNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ne-NP-SagarNeural2",
        "VoiceId": "(ne-NP, SagarNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "nl-BE",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "nl-BE-DenaNeural",
        "VoiceId": "(nl-BE, DenaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "nl-BE-ArnaudNeural",
        "VoiceId": "(nl-BE, ArnaudNeural)"
      }
    ]
  },
  {
    "LanguageKey": "nl-NL",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "nl-NL-FennaNeural",
        "VoiceId": "(nl-NL, FennaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "nl-NL-MaartenNeural",
        "VoiceId": "(nl-NL, MaartenNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "nl-NL-ColetteNeural",
        "VoiceId": "(nl-NL, ColetteNeural)"
      }
    ]
  },
  {
    "LanguageKey": "pl-PL",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "pl-PL-AgnieszkaNeural",
        "VoiceId": "(pl-PL, AgnieszkaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "pl-PL-MarekNeural",
        "VoiceId": "(pl-PL, MarekNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "pl-PL-ZofiaNeural",
        "VoiceId": "(pl-PL, ZofiaNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ps-AF",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ps-AF-LatifaNeural2",
        "VoiceId": "(ps-AF, LatifaNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ps-AF-GulNawazNeural2",
        "VoiceId": "(ps-AF, GulNawazNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "pt-BR",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "pt-BR-FranciscaNeural",
        "VoiceId": "(pt-BR, FranciscaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "pt-BR-AntonioNeural",
        "VoiceId": "(pt-BR, AntonioNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "pt-BR-BrendaNeural",
        "VoiceId": "(pt-BR, BrendaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "pt-BR-DonatoNeural",
        "VoiceId": "(pt-BR, DonatoNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "pt-BR-ElzaNeural",
        "VoiceId": "(pt-BR, ElzaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "pt-BR-FabioNeural",
        "VoiceId": "(pt-BR, FabioNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "pt-BR-GiovannaNeural",
        "VoiceId": "(pt-BR, GiovannaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "pt-BR-HumbertoNeural",
        "VoiceId": "(pt-BR, HumbertoNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "pt-BR-JulioNeural",
        "VoiceId": "(pt-BR, JulioNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "pt-BR-LeilaNeural",
        "VoiceId": "(pt-BR, LeilaNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "pt-BR-LeticiaNeural",
        "VoiceId": "(pt-BR, LeticiaNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "pt-BR-ManuelaNeural",
        "VoiceId": "(pt-BR, ManuelaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "pt-BR-NicolauNeural",
        "VoiceId": "(pt-BR, NicolauNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "pt-BR-ThalitaNeural",
        "VoiceId": "(pt-BR, ThalitaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "pt-BR-ValerioNeural",
        "VoiceId": "(pt-BR, ValerioNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "pt-BR-YaraNeural",
        "VoiceId": "(pt-BR, YaraNeural)"
      }
    ]
  },
  {
    "LanguageKey": "pt-PT",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "pt-PT-RaquelNeural",
        "VoiceId": "(pt-PT, RaquelNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "pt-PT-DuarteNeural",
        "VoiceId": "(pt-PT, DuarteNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "pt-PT-FernandaNeural",
        "VoiceId": "(pt-PT, FernandaNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ro-RO",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ro-RO-AlinaNeural",
        "VoiceId": "(ro-RO, AlinaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ro-RO-EmilNeural",
        "VoiceId": "(ro-RO, EmilNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ru-RU",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ru-RU-SvetlanaNeural",
        "VoiceId": "(ru-RU, SvetlanaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ru-RU-DmitryNeural",
        "VoiceId": "(ru-RU, DmitryNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "ru-RU-DariyaNeural",
        "VoiceId": "(ru-RU, DariyaNeural)"
      }
    ]
  },
  {
    "LanguageKey": "si-LK",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "si-LK-ThiliniNeural2",
        "VoiceId": "(si-LK, ThiliniNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "si-LK-SameeraNeural2",
        "VoiceId": "(si-LK, SameeraNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "sk-SK",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "sk-SK-ViktoriaNeural",
        "VoiceId": "(sk-SK, ViktoriaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "sk-SK-LukasNeural",
        "VoiceId": "(sk-SK, LukasNeural)"
      }
    ]
  },
  {
    "LanguageKey": "sl-SI",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "sl-SI-PetraNeural",
        "VoiceId": "(sl-SI, PetraNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "sl-SI-RokNeural",
        "VoiceId": "(sl-SI, RokNeural)"
      }
    ]
  },
  {
    "LanguageKey": "so-SO",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "so-SO-UbaxNeural2",
        "VoiceId": "(so-SO, UbaxNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "so-SO-MuuseNeural2",
        "VoiceId": "(so-SO, MuuseNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "sq-AL",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "sq-AL-AnilaNeural2",
        "VoiceId": "(sq-AL, AnilaNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "sq-AL-IlirNeural2",
        "VoiceId": "(sq-AL, IlirNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "sr-LATN-RS",
    "VoiceActors": [
      {
        "IsFemale": false,
        "VoiceActor": "sr-Latn-RS-NicholasNeural1,2",
        "VoiceId": "(sr-Latn, RS)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "sr-Latn-RS-SophieNeural1,2",
        "VoiceId": "(sr-Latn, RS)"
      }
    ]
  },
  {
    "LanguageKey": "sr-RS",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "sr-RS-SophieNeural2",
        "VoiceId": "(sr-RS, SophieNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "sr-RS-NicholasNeural2",
        "VoiceId": "(sr-RS, NicholasNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "su-ID",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "su-ID-TutiNeural2",
        "VoiceId": "(su-ID, TutiNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "su-ID-JajangNeural2",
        "VoiceId": "(su-ID, JajangNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "sv-SE",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "sv-SE-SofieNeural",
        "VoiceId": "(sv-SE, SofieNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "sv-SE-MattiasNeural",
        "VoiceId": "(sv-SE, MattiasNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "sv-SE-HilleviNeural",
        "VoiceId": "(sv-SE, HilleviNeural)"
      }
    ]
  },
  {
    "LanguageKey": "sw-KE",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "sw-KE-ZuriNeural2",
        "VoiceId": "(sw-KE, ZuriNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "sw-KE-RafikiNeural2",
        "VoiceId": "(sw-KE, RafikiNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "sw-TZ",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "sw-TZ-RehemaNeural",
        "VoiceId": "(sw-TZ, RehemaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "sw-TZ-DaudiNeural",
        "VoiceId": "(sw-TZ, DaudiNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ta-IN",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ta-IN-PallaviNeural",
        "VoiceId": "(ta-IN, PallaviNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ta-IN-ValluvarNeural",
        "VoiceId": "(ta-IN, ValluvarNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ta-LK",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ta-LK-SaranyaNeural",
        "VoiceId": "(ta-LK, SaranyaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ta-LK-KumarNeural",
        "VoiceId": "(ta-LK, KumarNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ta-MY",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ta-MY-KaniNeural",
        "VoiceId": "(ta-MY, KaniNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ta-MY-SuryaNeural",
        "VoiceId": "(ta-MY, SuryaNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ta-SG",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ta-SG-VenbaNeural",
        "VoiceId": "(ta-SG, VenbaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ta-SG-AnbuNeural",
        "VoiceId": "(ta-SG, AnbuNeural)"
      }
    ]
  },
  {
    "LanguageKey": "te-IN",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "te-IN-ShrutiNeural",
        "VoiceId": "(te-IN, ShrutiNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "te-IN-MohanNeural",
        "VoiceId": "(te-IN, MohanNeural)"
      }
    ]
  },
  {
    "LanguageKey": "th-TH",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "th-TH-PremwadeeNeural",
        "VoiceId": "(th-TH, PremwadeeNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "th-TH-NiwatNeural",
        "VoiceId": "(th-TH, NiwatNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "th-TH-AcharaNeural",
        "VoiceId": "(th-TH, AcharaNeural)"
      }
    ]
  },
  {
    "LanguageKey": "tr-TR",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "tr-TR-EmelNeural",
        "VoiceId": "(tr-TR, EmelNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "tr-TR-AhmetNeural",
        "VoiceId": "(tr-TR, AhmetNeural)"
      }
    ]
  },
  {
    "LanguageKey": "uk-UA",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "uk-UA-PolinaNeural",
        "VoiceId": "(uk-UA, PolinaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "uk-UA-OstapNeural",
        "VoiceId": "(uk-UA, OstapNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ur-IN",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ur-IN-GulNeural",
        "VoiceId": "(ur-IN, GulNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ur-IN-SalmanNeural",
        "VoiceId": "(ur-IN, SalmanNeural)"
      }
    ]
  },
  {
    "LanguageKey": "ur-PK",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "ur-PK-UzmaNeural",
        "VoiceId": "(ur-PK, UzmaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "ur-PK-AsadNeural",
        "VoiceId": "(ur-PK, AsadNeural)"
      }
    ]
  },
  {
    "LanguageKey": "uz-UZ",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "uz-UZ-MadinaNeural2",
        "VoiceId": "(uz-UZ, MadinaNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "uz-UZ-SardorNeural2",
        "VoiceId": "(uz-UZ, SardorNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "vi-VN",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "vi-VN-HoaiMyNeural",
        "VoiceId": "(vi-VN, HoaiMyNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "vi-VN-NamMinhNeural",
        "VoiceId": "(vi-VN, NamMinhNeural)"
      }
    ]
  },
  {
    "LanguageKey": "wuu-CN",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "wuu-CN-XiaotongNeural2",
        "VoiceId": "(wuu-CN, XiaotongNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "wuu-CN-YunzheNeural2",
        "VoiceId": "(wuu-CN, YunzheNeural2)"
      }
    ]
  },
  {
    "LanguageKey": "yue-CN",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "yue-CN-XiaoMinNeural1,2",
        "VoiceId": "(yue-CN, XiaoMinNeural1,2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "yue-CN-YunSongNeural1,2",
        "VoiceId": "(yue-CN, YunSongNeural1,2)"
      }
    ]
  },
  {
    "LanguageKey": "zh-CN",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "zh-CN-XiaoxiaoNeural",
        "VoiceId": "(zh-CN, XiaoxiaoNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "zh-CN-YunxiNeural",
        "VoiceId": "(zh-CN, YunxiNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "zh-CN-YunjianNeural",
        "VoiceId": "(zh-CN, YunjianNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "zh-CN-XiaoyiNeural",
        "VoiceId": "(zh-CN, XiaoyiNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "zh-CN-YunyangNeural",
        "VoiceId": "(zh-CN, YunyangNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "zh-CN-XiaochenNeural",
        "VoiceId": "(zh-CN, XiaochenNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "zh-CN-XiaohanNeural",
        "VoiceId": "(zh-CN, XiaohanNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "zh-CN-XiaomengNeural",
        "VoiceId": "(zh-CN, XiaomengNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "zh-CN-XiaomoNeural",
        "VoiceId": "(zh-CN, XiaomoNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "zh-CN-XiaoqiuNeural",
        "VoiceId": "(zh-CN, XiaoqiuNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "zh-CN-XiaoruiNeural",
        "VoiceId": "(zh-CN, XiaoruiNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "zh-CN-XiaoshuangNeural",
        "VoiceId": "(zh-CN, XiaoshuangNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "zh-CN-XiaoxuanNeural",
        "VoiceId": "(zh-CN, XiaoxuanNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "zh-CN-XiaoyanNeural",
        "VoiceId": "(zh-CN, XiaoyanNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "zh-CN-XiaoyouNeural",
        "VoiceId": "(zh-CN, XiaoyouNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "zh-CN-XiaozhenNeural",
        "VoiceId": "(zh-CN, XiaozhenNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "zh-CN-YunfengNeural",
        "VoiceId": "(zh-CN, YunfengNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "zh-CN-YunhaoNeural",
        "VoiceId": "(zh-CN, YunhaoNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "zh-CN-YunxiaNeural",
        "VoiceId": "(zh-CN, YunxiaNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "zh-CN-YunyeNeural",
        "VoiceId": "(zh-CN, YunyeNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "zh-CN-YunzeNeural",
        "VoiceId": "(zh-CN, YunzeNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "zh-CN-XiaorouNeural1",
        "VoiceId": "(zh-CN, XiaorouNeural1)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "zh-CN-YunjieNeural1",
        "VoiceId": "(zh-CN, YunjieNeural1)"
      }
    ]
  },
  {
    "LanguageKey": "zh-CN-GUANGXI",
    "VoiceActors": [
      {
        "IsFemale": false,
        "VoiceActor": "zh-CN-guangxi-YunqiNeural1,2",
        "VoiceId": "(zh-CN, guangxi)"
      }
    ]
  },
  {
    "LanguageKey": "zh-CN-henan",
    "VoiceActors": [
      {
        "IsFemale": false,
        "VoiceActor": "zh-CN-henan-YundengNeural2",
        "VoiceId": "(zh-CN, henan)"
      }
    ]
  },
  {
    "LanguageKey": "zh-CN-liaoning",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "zh-CN-liaoning-XiaobeiNeural1,2",
        "VoiceId": "(zh-CN, liaoning)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "zh-CN-liaoning-YunbiaoNeural1,2",
        "VoiceId": "(zh-CN, liaoning)"
      }
    ]
  },
  {
    "LanguageKey": "zh-CN-shaanxi",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "zh-CN-shaanxi-XiaoniNeural1,2",
        "VoiceId": "(zh-CN, shaanxi)"
      }
    ]
  },
  {
    "LanguageKey": "zh-CN-shandong",
    "VoiceActors": [
      {
        "IsFemale": false,
        "VoiceActor": "zh-CN-shandong-YunxiangNeural2",
        "VoiceId": "(zh-CN, shandong)"
      }
    ]
  },
  {
    "LanguageKey": "zh-CN-sichuan",
    "VoiceActors": [
      {
        "IsFemale": false,
        "VoiceActor": "zh-CN-sichuan-YunxiNeural1,2",
        "VoiceId": "(zh-CN, sichuan)"
      }
    ]
  },
  {
    "LanguageKey": "zh-HK",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "zh-HK-HiuMaanNeural",
        "VoiceId": "(zh-HK, HiuMaanNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "zh-HK-WanLungNeural",
        "VoiceId": "(zh-HK, WanLungNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "zh-HK-HiuGaaiNeural",
        "VoiceId": "(zh-HK, HiuGaaiNeural)"
      }
    ]
  },
  {
    "LanguageKey": "zh-TW",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "zh-TW-HsiaoChenNeural",
        "VoiceId": "(zh-TW, HsiaoChenNeural)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "zh-TW-YunJheNeural",
        "VoiceId": "(zh-TW, YunJheNeural)"
      },
      {
        "IsFemale": true,
        "VoiceActor": "zh-TW-HsiaoYuNeural",
        "VoiceId": "(zh-TW, HsiaoYuNeural)"
      }
    ]
  },
  {
    "LanguageKey": "zu-ZA",
    "VoiceActors": [
      {
        "IsFemale": true,
        "VoiceActor": "zu-ZA-ThandoNeural2",
        "VoiceId": "(zu-ZA, ThandoNeural2)"
      },
      {
        "IsFemale": false,
        "VoiceActor": "zu-ZA-ThembaNeural2",
        "VoiceId": "(zu-ZA, ThembaNeural2)"
      }
    ]
  }
]
               
                  

Let's look at the source code for calling the TextToSpeechUtil.cs shown above from a MAUI Blazor app view, Index.razor The code below shown is two private methods that does the work of retrieving the audio file from the Azure Speeech Service by first loading up all the voice actor ids from a bundled json file of voice actors displayed above and deserialize this into a list of voice actors. Retrieving the audio file passes in the translated text of which to generate synthesized speedch for and also the target language, all available actor voices and preferred voice actor id, if set. Retrieved is metadata and the audio file, in a MP3 file format. The file format is recognized by for example Windows withouth having to have any codec libraries installed in addition. Index.razor (Inside the @code block { .. } of that razor file)


 private async Task<TextToSpeechLanguage[]> GetActorVoices()
    {
        //https://learn.microsoft.com/en-us/azure/ai-services/speech-service/language-support?tabs=tts
        Stream actorVoicesStream = await FileSystem.OpenAppPackageFileAsync("voicebook.json");
        using StreamReader sr = new StreamReader(actorVoicesStream);
        string actorVoicesJson = string.Empty;
        string line;

        while ((line = sr.ReadLine()) != null)
        {
            //Console.WriteLine(line);
            actorVoicesJson += line;
        }

        var actorVoices = JsonSerializer.Deserialize<TextToSpeechLanguage[]>(actorVoicesJson);
        return actorVoices;
    }

    private async void SpeakText()
    {
        await Submit();
        var actorVoices = await GetActorVoices();
        TextToSpeechResult textToSpeechResult = await TextToSpeechUtil.GetSpeechFromText(Model.TranslatedText, Model.TargetLanguage, actorVoices, Model.PreferredVoiceActorId);

        Model.ActiveVoiceActorId = textToSpeechResult.VoiceActorId;
        Model.Transcript = textToSpeechResult.Transcript;
        Model.AvailableVoiceActorIds = textToSpeechResult.AvailableVoiceActorIds;
        Model.AdditionalVoiceDataMetaInformation = $"Byte size voice data: {textToSpeechResult?.VoiceData?.Length}, Audio output format: {textToSpeechResult.OutputFormat}";

        var voiceFolder = Path.Combine(FileSystem.Current.AppDataDirectory, "Resources", "Raw");
        if (!Directory.Exists(voiceFolder))
        {
            Directory.CreateDirectory(voiceFolder);
        }
        string voiceFile = "textToSpeechVoiceOutput_" + Model.TargetLanguage + Guid.NewGuid().ToString("N") + ".mpga";
        string voiceRelativeFile = Path.Combine(voiceFile);

        string voiceFileFullPath = Path.Combine(voiceFolder, voiceFile);
        await File.WriteAllBytesAsync(voiceFileFullPath, textToSpeechResult.VoiceData);
        Stream voiceStream = File.OpenRead(voiceFileFullPath);

        StateHasChanged();

        var player = AudioManager.CreatePlayer(voiceStream);
        player.Play();
    }



A screenshot shows how the DEMO app now looks like. You can translate text into other language and then have speech synthesis in Azure AI Cognitive Service generate a realistic audio speech of the translated text so you can also see how the text not only is translated, but also pronounced.