Skip to content

Commit 5125f4b

Browse files
committed
Add AWS Service and Polly support for TTS
This replaces the old tts_endpoint property and old "hacky" TTS support. Now TTS is accessed exclusively via AWS/Polly. AWS Service layer is considered necessary for now, however once I eventually add optional Services support this might change.
1 parent 7b933e3 commit 5125f4b

22 files changed

Lines changed: 671 additions & 71 deletions

LukeBot.API/Exception/InvalidClientDataException.cs

Lines changed: 0 additions & 9 deletions
This file was deleted.

LukeBot.API/Flow.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ namespace LukeBot.API
77
{
88
abstract class Flow
99
{
10-
private readonly string CLIENT_ID_PROP_NAME = "client_id";
11-
private readonly string CLIENT_SECRET_PROP_NAME = "client_secret";
12-
1310
protected string mService;
1411
protected string mClientID;
1512
protected string mClientSecret;
@@ -38,16 +35,16 @@ protected Flow(string service, string authURL, string tokenURL, string revokeURL
3835
mTokenURL = tokenURL;
3936
mRevokeURL = revokeURL;
4037

41-
mClientID = ReadFromConfig(CLIENT_ID_PROP_NAME);
38+
mClientID = ReadFromConfig(Common.Constants.PROP_STORE_CLIENT_ID_PROP_NAME);
4239
if (mClientID == Common.Constants.DEFAULT_CLIENT_ID_NAME)
4340
{
44-
throw new InvalidClientDataException("Client ID for {0} not set in Property Store", mService);
41+
throw new InvalidCredentialsException(mService);
4542
}
4643

47-
mClientSecret = ReadFromConfig(CLIENT_SECRET_PROP_NAME);
44+
mClientSecret = ReadFromConfig(Common.Constants.PROP_STORE_CLIENT_SECRET_PROP_NAME);
4845
if (mClientSecret == Common.Constants.DEFAULT_CLIENT_SECRET_NAME)
4946
{
50-
throw new InvalidClientDataException("Client secret for {0} not set in Property Store", mService);
47+
throw new InvalidCredentialsException(mService);
5148
}
5249
}
5350

LukeBot.AWS.Impl/AWSCredentials.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using System;
2+
using LukeBot.Common;
3+
using LukeBot.Config;
4+
5+
6+
namespace LukeBot.AWS.Impl
7+
{
8+
internal class AWSCredentials
9+
{
10+
private static readonly Config.Path CLIENT_ID_PATH = Path.Start()
11+
.Push(Common.Constants.AWS_SERVICE_NAME)
12+
.Push(Common.Constants.PROP_STORE_CLIENT_ID_PROP_NAME);
13+
private static readonly Config.Path CLIENT_SECRET_PATH = Path.Start()
14+
.Push(Common.Constants.AWS_SERVICE_NAME)
15+
.Push(Common.Constants.PROP_STORE_CLIENT_SECRET_PROP_NAME);
16+
17+
private static string mClientID = String.Empty;
18+
private static string mClientSecret = String.Empty;
19+
20+
public static string ID
21+
{
22+
get
23+
{
24+
if (String.IsNullOrEmpty(mClientID))
25+
{
26+
mClientID = Conf.Get<string>(CLIENT_ID_PATH);
27+
if (mClientID == Common.Constants.DEFAULT_CLIENT_ID_NAME)
28+
{
29+
mClientID = String.Empty;
30+
throw new InvalidCredentialsException(Common.Constants.AWS_SERVICE_NAME);
31+
}
32+
}
33+
34+
return mClientID;
35+
}
36+
}
37+
38+
public static string Secret
39+
{
40+
get
41+
{
42+
if (String.IsNullOrEmpty(mClientSecret))
43+
{
44+
mClientSecret = Conf.Get<string>(CLIENT_SECRET_PATH);
45+
if (mClientSecret == Common.Constants.DEFAULT_CLIENT_SECRET_NAME)
46+
{
47+
mClientSecret = String.Empty;
48+
throw new InvalidCredentialsException(Common.Constants.AWS_SERVICE_NAME);
49+
}
50+
}
51+
52+
return mClientSecret;
53+
}
54+
}
55+
}
56+
}

LukeBot.AWS.Impl/AWSService.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System.Collections.Generic;
2+
using LukeBot.AWS;
3+
using LukeBot.Common;
4+
using LukeBot.Logging;
5+
using Amazon.Auth;
6+
7+
8+
namespace LukeBot.AWS.Impl
9+
{
10+
public class AWSService : IAWSService
11+
{
12+
Polly mPolly;
13+
14+
private AWSService()
15+
{
16+
}
17+
18+
static public IAWSService Create()
19+
{
20+
return new AWSService();
21+
}
22+
23+
public string GetServiceDebugName()
24+
{
25+
return Common.Constants.AWS_SERVICE_NAME;
26+
}
27+
28+
public IEnumerable<string> GetServiceDependencies()
29+
{
30+
return new List<string>();
31+
}
32+
33+
public IPolly Polly()
34+
{
35+
return mPolly;
36+
}
37+
38+
public void RequestShutdown()
39+
{
40+
}
41+
42+
public void Run()
43+
{
44+
mPolly = new();
45+
46+
Logger.Log().Info("AWS Service layer running");
47+
}
48+
49+
public void WaitForShutdown()
50+
{
51+
}
52+
}
53+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
using LukeBot.Common;
3+
4+
5+
namespace LukeBot.AWS.Impl
6+
{
7+
public class PollyException: LukeBot.Common.Exception
8+
{
9+
public PollyException(string reasonFmt, params object[] args)
10+
: base("Polly request failed: " + String.Format(reasonFmt, args))
11+
{}
12+
}
13+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<Configurations>Debug;Release;SecureDebug</Configurations>
6+
</PropertyGroup>
7+
8+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
9+
<DefineConstants>DEBUG;TRACE</DefineConstants>
10+
<OutputPath>$(SolutionDir)Bin\$(Configuration)\</OutputPath>
11+
<IntermediateOutputPath>$(SolutionDir)Obj\$(MSBuildProjectName)\$(Configuration)\</IntermediateOutputPath>
12+
</PropertyGroup>
13+
14+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='SecureDebug|AnyCPU'">
15+
<DefineConstants>DEBUG;TRACE;ENABLE_SECURE_LOGS</DefineConstants>
16+
<OutputPath>$(SolutionDir)Bin\$(Configuration)\</OutputPath>
17+
<IntermediateOutputPath>$(SolutionDir)Obj\$(MSBuildProjectName)\$(Configuration)\</IntermediateOutputPath>
18+
</PropertyGroup>
19+
20+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
21+
<DefineConstants>TRACE</DefineConstants>
22+
<OutputPath>$(SolutionDir)Bin\$(Configuration)\</OutputPath>
23+
<IntermediateOutputPath>$(SolutionDir)Obj\$(MSBuildProjectName)\$(Configuration)\</IntermediateOutputPath>
24+
</PropertyGroup>
25+
26+
<ItemGroup>
27+
<PackageReference Include="AWSSDK.Core" Version="4.0.6.1" />
28+
<PackageReference Include="AWSSDK.Polly" Version="4.0.6.10" />
29+
</ItemGroup>
30+
31+
<ItemGroup>
32+
<ProjectReference Include="..\LukeBot.AWS\LukeBot.AWS.csproj" />
33+
<ProjectReference Include="..\LukeBot.Common\LukeBot.Common.csproj" />
34+
<ProjectReference Include="..\LukeBot.Logging\LukeBot.Logging.csproj" />
35+
<ProjectReference Include="..\LukeBot.Config\LukeBot.Config.csproj" />
36+
<ProjectReference Include="..\LukeBot.Services\LukeBot.Services.csproj" />
37+
</ItemGroup>
38+
39+
</Project>

LukeBot.AWS.Impl/Polly.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System.Collections.Generic;
2+
using LukeBot.AWS;
3+
using LukeBot.Common;
4+
using LukeBot.Config;
5+
using Amazon;
6+
using Amazon.Polly;
7+
using Amazon.Polly.Model;
8+
using System.Threading.Tasks;
9+
using System.IO;
10+
using System;
11+
using System.Net;
12+
13+
14+
namespace LukeBot.AWS.Impl
15+
{
16+
internal class Polly: IPolly
17+
{
18+
private AmazonPollyConfig mConfig;
19+
private AmazonPollyClient mClient;
20+
21+
private VoiceId PollyVoiceToVoiceId(PollyVoice voice)
22+
{
23+
switch (voice)
24+
{
25+
case PollyVoice.Brian: return VoiceId.Brian;
26+
case PollyVoice.Jacek: return VoiceId.Jacek;
27+
default: throw new ArgumentException(String.Format("Unknown Polly voice requested: {0}", voice));
28+
}
29+
}
30+
31+
internal Polly()
32+
{
33+
Config.Path awsRegionConfPath = Config.Path.Start()
34+
.Push(Common.Constants.AWS_SERVICE_NAME)
35+
.Push(Common.Constants.PROP_STORE_REGION_PROP_NAME);
36+
37+
mConfig = new();
38+
if (Conf.TryGet<string>(awsRegionConfPath, out string awsRegion))
39+
{
40+
mConfig.RegionEndpoint = RegionEndpoint.GetBySystemName(awsRegion);
41+
}
42+
else
43+
{
44+
mConfig.RegionEndpoint = RegionEndpoint.EUCentral1;
45+
}
46+
47+
mClient = new(AWSCredentials.ID, AWSCredentials.Secret, mConfig);
48+
}
49+
50+
public async Task<Stream> SynthesizeSpeech(PollyVoice voice, string text)
51+
{
52+
SynthesizeSpeechRequest request = new();
53+
request.OutputFormat = OutputFormat.Ogg_vorbis;
54+
request.Engine = Engine.Standard;
55+
request.VoiceId = PollyVoiceToVoiceId(voice);
56+
request.SampleRate = "44100";
57+
request.Text = text;
58+
59+
SynthesizeSpeechResponse response = await mClient.SynthesizeSpeechAsync(request);
60+
if (response.HttpStatusCode != HttpStatusCode.OK)
61+
{
62+
throw new PollyException("SynthesizeSpeech API returned code {0} ({1})", response.HttpStatusCode, (int)response.HttpStatusCode);
63+
}
64+
65+
return response.AudioStream;
66+
}
67+
}
68+
}

LukeBot.AWS/IAWSService.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using LukeBot.Services;
2+
3+
namespace LukeBot.AWS
4+
{
5+
public interface IAWSService: IService<IAWSService>
6+
{
7+
IPolly Polly();
8+
}
9+
}

LukeBot.AWS/IPolly.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.IO;
2+
using System.Threading.Tasks;
3+
using LukeBot.Services;
4+
5+
namespace LukeBot.AWS
6+
{
7+
public interface IPolly
8+
{
9+
Task<Stream> SynthesizeSpeech(PollyVoice voice, string text);
10+
}
11+
}

LukeBot.AWS/LukeBot.AWS.csproj

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<Configurations>Debug;Release;SecureDebug</Configurations>
6+
</PropertyGroup>
7+
8+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
9+
<DefineConstants>DEBUG;TRACE</DefineConstants>
10+
<OutputPath>$(SolutionDir)Bin\$(Configuration)\</OutputPath>
11+
<IntermediateOutputPath>$(SolutionDir)Obj\$(MSBuildProjectName)\$(Configuration)\</IntermediateOutputPath>
12+
</PropertyGroup>
13+
14+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='SecureDebug|AnyCPU'">
15+
<DefineConstants>DEBUG;TRACE;ENABLE_SECURE_LOGS</DefineConstants>
16+
<OutputPath>$(SolutionDir)Bin\$(Configuration)\</OutputPath>
17+
<IntermediateOutputPath>$(SolutionDir)Obj\$(MSBuildProjectName)\$(Configuration)\</IntermediateOutputPath>
18+
</PropertyGroup>
19+
20+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
21+
<DefineConstants>TRACE</DefineConstants>
22+
<OutputPath>$(SolutionDir)Bin\$(Configuration)\</OutputPath>
23+
<IntermediateOutputPath>$(SolutionDir)Obj\$(MSBuildProjectName)\$(Configuration)\</IntermediateOutputPath>
24+
</PropertyGroup>
25+
26+
<ItemGroup>
27+
<ProjectReference Include="..\LukeBot.Services\LukeBot.Services.csproj" />
28+
</ItemGroup>
29+
30+
</Project>

0 commit comments

Comments
 (0)