From 4edd94b302b443e8b4cf12916b62f182cab72905 Mon Sep 17 00:00:00 2001 From: Elijah Date: Tue, 2 Jul 2024 20:13:00 -0400 Subject: [PATCH] Add project files. --- SAPIServer.sln | 25 +++++++ SAPIServer/HTTPServer.cs | 93 +++++++++++++++++++++++++++ SAPIServer/Program.cs | 67 +++++++++++++++++++ SAPIServer/Properties/AssemblyInfo.cs | 36 +++++++++++ SAPIServer/SAPIServer.csproj | 58 +++++++++++++++++ SAPIServer/SynthesizePayload.cs | 8 +++ SAPIServer/VoicesResponse.cs | 7 ++ SAPIServer/packages.config | 4 ++ 8 files changed, 298 insertions(+) create mode 100644 SAPIServer.sln create mode 100644 SAPIServer/HTTPServer.cs create mode 100644 SAPIServer/Program.cs create mode 100644 SAPIServer/Properties/AssemblyInfo.cs create mode 100644 SAPIServer/SAPIServer.csproj create mode 100644 SAPIServer/SynthesizePayload.cs create mode 100644 SAPIServer/VoicesResponse.cs create mode 100644 SAPIServer/packages.config diff --git a/SAPIServer.sln b/SAPIServer.sln new file mode 100644 index 0000000..344da53 --- /dev/null +++ b/SAPIServer.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.34931.43 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SAPIServer", "SAPIServer\SAPIServer.csproj", "{BF824074-4C4E-4DE1-8DCA-F022682B00E1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BF824074-4C4E-4DE1-8DCA-F022682B00E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BF824074-4C4E-4DE1-8DCA-F022682B00E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BF824074-4C4E-4DE1-8DCA-F022682B00E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BF824074-4C4E-4DE1-8DCA-F022682B00E1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {80220FEF-238D-4B58-B201-8960474A4AC1} + EndGlobalSection +EndGlobal diff --git a/SAPIServer/HTTPServer.cs b/SAPIServer/HTTPServer.cs new file mode 100644 index 0000000..fe34f38 --- /dev/null +++ b/SAPIServer/HTTPServer.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text; +using System.Threading; + +namespace SAPIServer +{ + /// + /// Simple routing HTTP server for .NET Framework + /// + class HTTPServer + { + private bool listening = false; + private HttpListener listener; + private Dictionary> GETListeners; + private Dictionary> POSTListeners; + + public HTTPServer() + { + listener = new HttpListener(); + this.GETListeners = new Dictionary>(); + this.POSTListeners = new Dictionary>(); + } + + public void Get(string uri, Func handler) + { + if (GETListeners.ContainsKey(uri)) throw new InvalidOperationException("A handler by that URI already exists."); + if (!uri.StartsWith("/")) throw new ArgumentException("URIs must start with /"); + this.GETListeners.Add(uri, handler); + } + + public void Post(string uri, Func handler) + { + if (POSTListeners.ContainsKey(uri)) throw new InvalidOperationException("A handler by that URI already exists."); + if (!uri.StartsWith("/")) throw new ArgumentException("URIs must start with /"); + this.POSTListeners.Add(uri, handler); + } + + public void Listen(ushort port) + { + if (listening) throw new InvalidOperationException("This HTTPServer is already listening."); + listening = true; + listener.Prefixes.Add(string.Format("http://127.0.0.1:{0}/", port)); + listener.Start(); + while (listener.IsListening) + { + var context = listener.GetContext(); + ThreadPool.QueueUserWorkItem(_ => + { + Dictionary> handlerDict; + // TODO: Make query params parsable + var uri = context.Request.RawUrl.Split('?')[0]; + byte[] response; + switch (context.Request.HttpMethod) + { + case "GET": + { + handlerDict = GETListeners; + break; + } + case "POST": + { + handlerDict = POSTListeners; + break; + } + default: + { + response = Encoding.UTF8.GetBytes($"Method not allowed: {context.Request.HttpMethod}"); + context.Response.StatusCode = 405; + context.Response.ContentType = "text/plain"; + context.Response.ContentLength64 = response.Length; + context.Response.OutputStream.Write(response, 0, response.Length); + context.Response.OutputStream.Close(); + return; + } + } + if (!handlerDict.TryGetValue(uri, out var handler)) + { + response = Encoding.UTF8.GetBytes($"No route defined for {uri}"); + context.Response.StatusCode = 404; + context.Response.ContentType = "text/plain"; + } + else response = handler(context); + context.Response.ContentLength64 = response.Length; + context.Response.OutputStream.Write(response, 0, response.Length); + context.Response.OutputStream.Close(); + Console.WriteLine($"[{DateTime.Now:u}] {context.Request.RemoteEndPoint.Address} - {context.Request.HttpMethod} {context.Request.RawUrl} - {context.Response.StatusCode}"); + }); + } + } + } +} diff --git a/SAPIServer/Program.cs b/SAPIServer/Program.cs new file mode 100644 index 0000000..f89db80 --- /dev/null +++ b/SAPIServer/Program.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Speech.Synthesis; +using System.Text; +using Newtonsoft.Json; + +namespace SAPIServer +{ + class Program + { + static void Main(string[] args) + { + var http = new HTTPServer(); + http.Get("/api/voices", ctx => + { + using (var synth = new SpeechSynthesizer()) + { + ctx.Response.ContentType = "application/json"; + return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new VoicesResponse + { + voices = synth.GetInstalledVoices().Select(v => v.VoiceInfo.Name).ToArray() + })); + } + }); + http.Post("/api/synthesize", ctx => + { + SynthesizePayload body; + try + { + string bodyraw; + using (var reader = new StreamReader(ctx.Request.InputStream)) + { + bodyraw = reader.ReadToEnd(); + } + body = JsonConvert.DeserializeObject(bodyraw); + } + catch (Exception e) + { + ctx.Response.StatusCode = 400; + return Encoding.UTF8.GetBytes("Bad payload"); + } + if (body == null || body.text == null || body.voice == null) + { + ctx.Response.StatusCode = 400; + return Encoding.UTF8.GetBytes("Bad payload"); + } + using (var ms = new MemoryStream()) + using (var synth = new SpeechSynthesizer()) + { + if (!synth.GetInstalledVoices().Any(v => v.VoiceInfo.Name == body.voice)) + { + ctx.Response.StatusCode = 400; + return Encoding.UTF8.GetBytes("Voice not found"); + } + synth.SelectVoice(body.voice); + synth.SetOutputToWaveStream(ms); + synth.Speak(body.text); + ctx.Response.ContentType = "audio/wav"; + return ms.ToArray(); + } + }); + http.Listen(8080); + } + } +} diff --git a/SAPIServer/Properties/AssemblyInfo.cs b/SAPIServer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..149e40d --- /dev/null +++ b/SAPIServer/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SAPIServer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SAPIServer")] +[assembly: AssemblyCopyright("Copyright © 2024")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("bf824074-4c4e-4de1-8dca-f022682b00e1")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SAPIServer/SAPIServer.csproj b/SAPIServer/SAPIServer.csproj new file mode 100644 index 0000000..79c9ee9 --- /dev/null +++ b/SAPIServer/SAPIServer.csproj @@ -0,0 +1,58 @@ + + + + + Debug + AnyCPU + {BF824074-4C4E-4DE1-8DCA-F022682B00E1} + Exe + SAPIServer + SAPIServer + v4.0 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Newtonsoft.Json.13.0.3\lib\net40\Newtonsoft.Json.dll + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SAPIServer/SynthesizePayload.cs b/SAPIServer/SynthesizePayload.cs new file mode 100644 index 0000000..333565d --- /dev/null +++ b/SAPIServer/SynthesizePayload.cs @@ -0,0 +1,8 @@ +namespace SAPIServer +{ + class SynthesizePayload + { + public string voice { get; set; } + public string text { get; set; } + } +} diff --git a/SAPIServer/VoicesResponse.cs b/SAPIServer/VoicesResponse.cs new file mode 100644 index 0000000..214d8e7 --- /dev/null +++ b/SAPIServer/VoicesResponse.cs @@ -0,0 +1,7 @@ +namespace SAPIServer +{ + class VoicesResponse + { + public string[] voices { get; set; } + } +} diff --git a/SAPIServer/packages.config b/SAPIServer/packages.config new file mode 100644 index 0000000..e521d78 --- /dev/null +++ b/SAPIServer/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file