/** * This method reads the key specified in PollyProperties and sets it as the new AWS profile. * At the moment, the region is always set to EU which is something you might want to care for. * Except for setting up the account, it is not used or verified at this point. * The method only fails if there is something wrong with the KeyFile. */ private void SetAwsProfile(PollyProperties pollyProps) { // Read the Credentials and store them shortly in an array var path = pollyProps.ApiKey; var credentialFile = new Dictionary <string, string>(); foreach (var row in File.ReadAllLines(path)) { credentialFile.Add(row.Split('=')[0], string.Join("=", row.Split('=').Skip(1).ToArray())); } // Create Amazon Credentials var options = new CredentialProfileOptions { AccessKey = credentialFile["AWSAccessKeyId"], SecretKey = credentialFile["AWSSecretKey"] }; // Make Amazon Credential profile var profile = new Amazon.Runtime.CredentialManagement.CredentialProfile("polly_profile", options); profile.Region = RegionEndpoint.EUCentral1; this._awsProfile = profile; }
/* * Makes a new savepoint at index+1. * If the point is already known, do nothing. * If a new Point is created while the Memento is in a position of the past, * all history after the current point will be lost. */ public void MakeMemento(PollyProperties ps) { var memento = (PollyProperties)ps.Clone(); // Short-Circuit: // memento is already in history - do nothing, return early. if (_history.Contains(memento)) { return; } // Case 1: Current Index is last of List if (_history.Count == 0 || _index == -1 || _index == _history.Count - 1) { // "Just" add a copy of current Properties to Memento List _history.Add(memento); // Increase Index by 1 _index++; } // Case 2: Current Index is not last of the list else { // Drop all items after index, "make current index last index" this._history = _history.GetRange(0, this._index); // Do Case 1 _history.Add(memento); _index++; } }
public object Clone() { // Cloning is rather easy, as Strings are pass by value (so is the int of sampling rate) var clone = new PollyProperties() { Voice = this.Voice, ApiKey = this.ApiKey, TextToPlay = this.TextToPlay, SamplingRate = this.SamplingRate }; return(clone); }
/** * This method reads the polly props and creates a hashcode for it. * The used hash-method is MD5, which gets cut down to 8 digits. * The number of digits is at the moment hardcoded in the PollyCaller as a constant. * The hashed properties are Text, Voice & SamplingRate. */ private string DeriveTemporaryName(PollyProperties pollyProps) { var input = pollyProps.TextToPlay + "+" + pollyProps.Voice + "+" + pollyProps.SamplingRate; string output; using (var provider = System.Security.Cryptography.MD5.Create()) { var builder = new StringBuilder(); foreach (byte b in provider.ComputeHash(Encoding.UTF8.GetBytes(input))) { builder.Append(b.ToString("x2").ToLower()); } output = builder.ToString(); } return(output.Substring(0, PollyCaller.HashCodeLength)); }
/* * This method reads the polly properties and audio-properties, * then it checks if the wanted combination of text+voice is known already. * If the text is known, it will be saved to the given filename, * otherwise it will be created by Amazon, stored locally and then saved. */ public void SaveToFile(string mp3Filename, PollyProperties pollyProps) { // There was an issue, when the stream was already played. // If that was the case, the stream was "at an end" and could not be saved somewhere (file was just empty). // So if the sound was played, there should be a temp file for it which we just move. // If the file is not there - remove the key and run again. SetAwsProfile(pollyProps); string tempName = DeriveTemporaryName(pollyProps); string tempFilePath = Path.GetFullPath(tempName + ".mp3", _soundDir.FullName); if (_knownHashes.ContainsKey(tempName)) { if (File.Exists(tempFilePath)) { File.Copy(tempFilePath, mp3Filename); } else { // The file got lost - that's bad. // Remove the key, and re-run again. This will also create the file. _knownHashes.Remove(tempName); SaveToFile(mp3Filename, pollyProps); } } else // The hash is not known - create it and make the file + temp file { Stream audioStream = MakeAwsCall(pollyProps); _knownHashes[tempName] = audioStream; // I am not 100% sure, but overwriting did not properly work for audio-streams. if (File.Exists(mp3Filename)) { File.Delete(mp3Filename); } // use two file-streams, to still save it to temp using (FileStream fs = File.Create(mp3Filename)) using (FileStream tfs = File.Create(tempFilePath)) { audioStream.CopyTo(fs); audioStream.CopyTo(tfs); fs.Flush(); tfs.Flush(); } } }
/* * This method reads the polly properties and audioproperties, * then it checks if the wanted combination of text+voice is known already. * If the text is known, it will be played to the selected audiodevices, * otherwise it will be created by Amazon, stored locally and then played. */ public void Call(PollyProperties pollyProps) { SetAwsProfile(pollyProps); var tempName = DeriveTemporaryName(pollyProps); var tempFilePath = Path.GetFullPath(tempName + ".mp3", _soundDir.FullName); Stream audioStream = null; if (_knownHashes.ContainsKey(tempName)) { audioStream = _knownHashes[tempName]; } else { audioStream = MakeAwsCall(pollyProps); _knownHashes[tempName] = audioStream; } if (!File.Exists(tempFilePath)) { /*TODO: Without the existence check, the line below throws a marshal-error * /*The file-handle does not seem to be closed properly. */ using (FileStream fs = File.Create(tempFilePath)) { audioStream.CopyTo(fs); fs.Flush(); } } // The audio-devices must be decremented by one, as the "default" for NAudio is -1 while its 0 for Windows. PlaySound(tempFilePath, AudioProps.DeviceA - 1, AudioProps.VolumeA); // Just play the second audio if there is a different selected if (AudioProps.DeviceA != AudioProps.DeviceB) { PlaySound(tempFilePath, AudioProps.DeviceB - 1, AudioProps.VolumeB); } }
/* * This method reads the polly properties and the ready-made AWS Profile to send a polly request. * It returns the Stream of the MP3 created. * * This method will likely fail in three ways: * 1) The Profile is bad * 2) The user is offline * 3) The specified content for the request is faulty * * I tried to address the 3) with value checks in the frontend, the others are not checked for at the moment. */ private Stream MakeAwsCall(PollyProperties pollyProps) { // Store the Amazon Profile in the Credentials Engine for later use var netSdkFile = new NetSDKCredentialsFile(); netSdkFile.RegisterProfile(_awsProfile); // This chain uses the credentials to create a token for usage, // Later the token is used in the Credentials // The chain stores it into awsCredentials, that is used for the client. var chain = new CredentialProfileStoreChain(); AWSCredentials awsCredentials; Stream audioStream = null; // use awsCredentials if (chain.TryGetAWSCredentials("polly_profile", out awsCredentials)) { using (var client = new AmazonPollyClient(awsCredentials, _awsProfile.Region)) { var response = client.SynthesizeSpeechAsync(new SynthesizeSpeechRequest { OutputFormat = "mp3", SampleRate = pollyProps.SamplingRate.ToString(), Text = pollyProps.TextToPlay, TextType = "text", VoiceId = pollyProps.Voice // One of Hans, Jenny , ... }); response.Wait(); var res = response.Result; audioStream = res.AudioStream; } } return(audioStream); }