├── .github └── workflows │ └── dotnet.yml ├── .gitignore ├── .vs └── Edge_tts_sharp │ ├── DesignTimeBuild │ └── .dtbcache.v2 │ └── v17 │ ├── .futdcache.v2 │ └── .suo ├── Edge_tts_sharp.sln ├── Edge_tts_sharp ├── Edge_tts.cs ├── Edge_tts_sharp.csproj ├── Edge_tts_sharp.csproj.user ├── Model │ ├── Log.cs │ ├── PlayOption.cs │ └── eVoice.cs ├── Properties │ └── PublishProfiles │ │ ├── FolderProfile.pubxml │ │ ├── FolderProfile.pubxml.user │ │ ├── FolderProfile1.pubxml │ │ └── FolderProfile1.pubxml.user ├── Source │ └── VoiceList.json ├── Tools.cs ├── Utils │ ├── Audio.cs │ ├── AudioPlayer.cs │ └── AudioStreamer.cs └── Wss.cs ├── Logo.ico ├── MenuLogo.png ├── README.md └── edge_tts_test ├── Program.cs ├── Properties └── PublishProfiles │ ├── FolderProfile.pubxml │ └── FolderProfile.pubxml.user ├── edge_tts_test.csproj └── edge_tts_test.csproj.user /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a .NET project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net 3 | 4 | name: .NET 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Setup .NET 18 | uses: actions/setup-dotnet@v3 19 | with: 20 | dotnet-version: 8.0.x 21 | - name: Restore dependencies 22 | run: dotnet restore 23 | working-directory: ./Edge_tts_sharp/ 24 | - name: Build 25 | run: dotnet build --configuration Release --no-restore 26 | working-directory: ./Edge_tts_sharp/ 27 | - name: Publish the package to nuget.org 28 | run: dotnet nuget push */bin/Release/*.nupkg -k $NUGET_AUTH_TOKEN -s https://api.nuget.org/v3/index.json 29 | env: 30 | NUGET_AUTH_TOKEN: ${{secrets.NUGET_API_KEY}} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | 4 | .mp3 5 | .web 6 | .vs -------------------------------------------------------------------------------- /.vs/Edge_tts_sharp/DesignTimeBuild/.dtbcache.v2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Entity-Now/Edge_tts_sharp/ff46bfdc49dcf5287b6b6e0bffa2669792884220/.vs/Edge_tts_sharp/DesignTimeBuild/.dtbcache.v2 -------------------------------------------------------------------------------- /.vs/Edge_tts_sharp/v17/.futdcache.v2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Entity-Now/Edge_tts_sharp/ff46bfdc49dcf5287b6b6e0bffa2669792884220/.vs/Edge_tts_sharp/v17/.futdcache.v2 -------------------------------------------------------------------------------- /.vs/Edge_tts_sharp/v17/.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Entity-Now/Edge_tts_sharp/ff46bfdc49dcf5287b6b6e0bffa2669792884220/.vs/Edge_tts_sharp/v17/.suo -------------------------------------------------------------------------------- /Edge_tts_sharp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.7.34024.191 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Edge_tts_sharp", "Edge_tts_sharp\Edge_tts_sharp.csproj", "{7C2F7F0D-9123-45AD-A4E6-590468CEFAAB}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "edge_tts_test", "edge_tts_test\edge_tts_test.csproj", "{8B04214D-AAA8-4E12-B77C-458ADD493179}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {7C2F7F0D-9123-45AD-A4E6-590468CEFAAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {7C2F7F0D-9123-45AD-A4E6-590468CEFAAB}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {7C2F7F0D-9123-45AD-A4E6-590468CEFAAB}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {7C2F7F0D-9123-45AD-A4E6-590468CEFAAB}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {8B04214D-AAA8-4E12-B77C-458ADD493179}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {8B04214D-AAA8-4E12-B77C-458ADD493179}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {8B04214D-AAA8-4E12-B77C-458ADD493179}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {8B04214D-AAA8-4E12-B77C-458ADD493179}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {0A5ECD0E-D8F9-4AD8-A6EA-2BE95659EE80} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /Edge_tts_sharp/Edge_tts.cs: -------------------------------------------------------------------------------- 1 | using Edge_tts_sharp.Model; 2 | using System; 3 | using System.Resources; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Net.WebSockets; 8 | using System.Reflection; 9 | using System.Text; 10 | using System.Text.RegularExpressions; 11 | using System.Threading.Tasks; 12 | using WebSocketSharp; 13 | using Edge_tts_sharp.Utils; 14 | using System.Threading; 15 | using System.Security.Cryptography; 16 | 17 | 18 | namespace Edge_tts_sharp 19 | { 20 | public class Edge_tts 21 | { 22 | /// 23 | /// 调试模式 24 | /// 25 | public static bool Debug = false; 26 | /// 27 | /// 同步模式 28 | /// 29 | public static bool Await = false; 30 | 31 | static string GenerateSecMsGecToken() 32 | { 33 | // 吵了下作业 34 | // 来自 https://github.com/STBBRD/EdgeTTS_dotNET_Framework/ 35 | // 来自 https://github.com/rany2/edge-tts/issues/290#issuecomment-2464956570 36 | var ticks = DateTime.Now.ToFileTimeUtc(); 37 | ticks -= ticks % 3_000_000_000; 38 | var str = ticks + "6A5AA1D4EAFF4E9FB37E23D68491D6F4"; 39 | return ToHexString(HashData(Encoding.ASCII.GetBytes(str))); 40 | } 41 | static string ToHexString(byte[] byteArray) 42 | { 43 | return BitConverter.ToString(byteArray).Replace("-", "").ToUpper(); 44 | } 45 | static byte[] HashData(byte[] data) 46 | { 47 | using (SHA256 sha256 = SHA256.Create()) 48 | { 49 | byte[] hashBytes = sha256.ComputeHash(data); 50 | return hashBytes; 51 | } 52 | } 53 | static string GetGUID() 54 | { 55 | return Guid.NewGuid().ToString().Replace("-",""); 56 | } 57 | /// 58 | /// 讲一个浮点型数值转换为百分比数值 59 | /// 60 | /// 61 | /// 62 | static string FromatPercentage(double input) 63 | { 64 | string output; 65 | 66 | if (input < 0) 67 | { 68 | output = input.ToString("+#;-#;0") + "%"; 69 | } 70 | else 71 | { 72 | output = input.ToString("+#;-#;0") + "%"; 73 | } 74 | return output; 75 | } 76 | static string ConvertToAudioFormatWebSocketString(string outputformat) 77 | { 78 | return "Content-Type:application/json; charset=utf-8\r\nPath:speech.config\r\n\r\n{\"context\":{\"synthesis\":{\"audio\":{\"metadataoptions\":{\"sentenceBoundaryEnabled\":\"false\",\"wordBoundaryEnabled\":\"false\"},\"outputFormat\":\"" + outputformat + "\"}}}}"; 79 | } 80 | /// 81 | /// 82 | /// 83 | /// 输出语言 84 | /// 音源名 85 | /// 语速,-100% - 100% 之间的值,无需传递百分号 86 | /// 87 | /// 88 | static string ConvertToSsmlText(string lang, string voice, int rate, int volume, string text) 89 | { 90 | return $"{text}"; 91 | } 92 | static string ConvertToSsmlWebSocketString(string requestId, string lang, string voice,int rate, int volume, string msg) 93 | { 94 | return $"X-RequestId:{requestId}\r\nContent-Type:application/ssml+xml\r\nPath:ssml\r\n\r\n{ConvertToSsmlText(lang, voice, rate, volume, msg)}"; 95 | } 96 | /// 97 | /// 语言转文本,将结果返回到回调函数中 98 | /// 99 | /// 播放参数 100 | /// 音源参数 101 | public static void Invoke(PlayOption option, eVoice voice, Action> callback, IProgress> progress = null) 102 | { 103 | var binary_delim = "Path:audio\r\n"; 104 | var sendRequestId = GetGUID(); 105 | var binary = new List(); 106 | bool IsTurnEnd = false; 107 | 108 | var wss = new Wss($"wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1?TrustedClientToken=6A5AA1D4EAFF4E9FB37E23D68491D6F4&Sec-MS-GEC={GenerateSecMsGecToken()}&Sec-MS-GEC-Version=1-130.0.2849.68"); 109 | wss.OnMessage += (sender, e) => 110 | { 111 | if (e.IsText) 112 | { 113 | var data = e.Data; 114 | var requestId = Regex.Match(data, @"X-RequestId:(?.*?)\r\n").Groups["requestId"].Value; 115 | if (data.Contains("Path:turn.start")) 116 | { 117 | // start of turn, ignore. 开始信号,不用处理 118 | } 119 | else if (data.Contains("Path:turn.end")) 120 | { 121 | // 返回内容 122 | if (binary.Count > 0) 123 | { 124 | callback?.Invoke(binary); 125 | } 126 | else 127 | { 128 | throw new Exception("返回值为空!"); 129 | } 130 | // end of turn, close stream. 结束信号,可主动关闭socket 131 | // 音频发送完毕后,最后还会收到一个表示音频结束的文本信息 132 | //wss.Close(); 133 | } 134 | else if (data.Contains("Path:response")) 135 | { 136 | // context response, ignore. 响应信号,无需处理 137 | } 138 | else 139 | { 140 | // 未知错误,通常不会发生 141 | } 142 | if (Debug) Console.WriteLine(e.Data); 143 | IsTurnEnd = true; 144 | } 145 | else if (e.IsBinary) 146 | { 147 | var data = e.RawData; 148 | var requestId = Regex.Match(e.Data, @"X-RequestId:(?.*?)\r\n").Groups["requestId"].Value; 149 | if (data[0] == 0x00 && data[1] == 0x67 && data[2] == 0x58) 150 | { 151 | // Last (empty) audio fragment. 空音频片段,代表音频发送结束 152 | } 153 | else 154 | { 155 | var index = Encoding.UTF8.GetString(data).IndexOf(binary_delim) + binary_delim.Length; 156 | var curVal = data.Skip(index); 157 | binary.AddRange(curVal); 158 | // 传出 159 | progress?.Report(curVal.ToList()); 160 | } 161 | } 162 | }; 163 | wss.OnColse += (sender, e) => 164 | { 165 | if (!string.IsNullOrEmpty(option.SavePath)) 166 | { 167 | File.WriteAllBytes(option.SavePath, binary.ToArray()); 168 | } 169 | }; 170 | wss.OnLog += (onmsg) => 171 | { 172 | if(Debug) Console.WriteLine($"[{onmsg.level.ToString()}] {onmsg.msg}"); 173 | }; 174 | if (wss.Run()) 175 | { 176 | wss.Send(ConvertToAudioFormatWebSocketString(voice.SuggestedCodec)); 177 | wss.Send(ConvertToSsmlWebSocketString(sendRequestId, voice.Locale, voice.Name, option.Rate, ((int)option.Volume * 100), option.Text)); 178 | } 179 | while (Await && !IsTurnEnd) 180 | { 181 | Thread.Sleep(10); 182 | } 183 | } 184 | /// 185 | /// 另存为mp3文件 186 | /// 187 | /// 播放参数 188 | /// 音源参数 189 | public static void SaveAudio(PlayOption option, eVoice voice) 190 | { 191 | if (string.IsNullOrEmpty(option.SavePath)) 192 | { 193 | throw new Exception("保存路径为空,请核对参数后重试."); 194 | } 195 | Invoke(option, voice, null); 196 | } 197 | /// 198 | /// 调用微软Edge接口,文字转语音 199 | /// 200 | /// 播放参数 201 | /// 音源参数 202 | public static void PlayText(PlayOption option, eVoice voice) 203 | { 204 | Invoke(option, voice, (_binary) => 205 | { 206 | Audio.PlayToByteAsync(_binary.ToArray(), option.Volume); 207 | }); 208 | } 209 | /// 210 | /// 获取一个`AudioPlayer`的对象 211 | /// 212 | /// 播放参数 213 | /// 音源参数 214 | /// 215 | public static AudioPlayer GetPlayer(PlayOption option, eVoice voice) 216 | { 217 | AudioPlayer player = null; 218 | Invoke(option, voice, (_binary) => 219 | { 220 | player = new AudioPlayer(_binary.ToArray(), option.Volume); 221 | }); 222 | while (player == null) 223 | { 224 | Thread.Sleep(10); 225 | } 226 | return player; 227 | } 228 | /// 229 | /// 同步等待播放音频结束 230 | /// 231 | /// 播放参数 232 | /// 音源参数 233 | //public static void PlayTextAsync(PlayOption option, eVoice voice) 234 | //{ 235 | // List buffer = new List(); 236 | // var audioStreamer = new Mp3AudioStreamer(); 237 | // var report = new Progress>((binary) => 238 | // { 239 | // audioStreamer.OnAudioReceived(binary.ToArray()); 240 | // }); 241 | // Invoke(option, voice, null, report); 242 | 243 | // audioStreamer.Stop(); 244 | //} 245 | 246 | /// 247 | /// 获取支持的音频列表 248 | /// 249 | /// 250 | public static List GetVoice() 251 | { 252 | var voiceList = Tools.GetEmbedText("Edge_tts_sharp.Source.VoiceList.json"); 253 | return Tools.StringToJson>(voiceList); 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /Edge_tts_sharp/Edge_tts_sharp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 1.1.4 9 | 10 | true 11 | 12 | true 13 | Edge_tts_sharp 14 | Entity-now 15 | 免费调用微软Edge浏览器文本转语音接口 16 | 17 | https://github.com/Entity-Now/Edge_tts_sharp 18 | https://github.com/Entity-Now/Edge_tts_sharp 19 | README.md 20 | MenuLogo.png 21 | git 22 | 099b9a5e-4ebd-4672-8871-9986815c01f8 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | Never 38 | 39 | 40 | 41 | 42 | 43 | True 44 | \ 45 | 46 | 47 | True 48 | \ 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Edge_tts_sharp/Edge_tts_sharp.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | <_LastSelectedProfileId>D:\Language\cs\Edge_tts_sharp\Edge_tts_sharp\Properties\PublishProfiles\FolderProfile1.pubxml 5 | 6 | -------------------------------------------------------------------------------- /Edge_tts_sharp/Model/Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Edge_tts_sharp.Model 6 | { 7 | public enum level 8 | { 9 | info, 10 | warning, 11 | error 12 | } 13 | public class Log 14 | { 15 | public string msg { get; set; } 16 | public level level { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Edge_tts_sharp/Model/PlayOption.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Edge_tts_sharp.Model 6 | { 7 | /// 8 | /// 播放音频配置参数 9 | /// 10 | public class PlayOption 11 | { 12 | /// 13 | /// 播放内容 14 | /// 15 | public string Text { get; set; } 16 | /// 17 | /// 语速,是一个-100 - 100的数值 18 | /// 19 | public int Rate { get; set; } = 0; 20 | /// 21 | /// 音量,是一个0 - 1的浮点数值 22 | /// 23 | public float Volume { get; set; } = 1.0f; 24 | /// 25 | /// 音频保存地址 26 | /// 27 | public string SavePath { get; set; } = string.Empty; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Edge_tts_sharp/Model/eVoice.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Edge_tts_sharp.Model 6 | { 7 | 8 | public class eVoice 9 | { 10 | public string Name { get; set; } 11 | public string ShortName { get; set; } 12 | public string Gender { get; set; } 13 | public string Locale { get; set; } 14 | public string SuggestedCodec { get; set; } 15 | public string FriendlyName { get; set; } 16 | public string Status { get; set; } 17 | public Voicetag VoiceTag { get; set; } 18 | } 19 | 20 | public class Voicetag 21 | { 22 | public string[] ContentCategories { get; set; } 23 | public string[] VoicePersonalities { get; set; } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Edge_tts_sharp/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | <<<<<<< HEAD 10 | bin\Release\netstandard2.0\publish\ 11 | ======= 12 | bin\Release\ 13 | >>>>>>> d90d7c952b02e74516234963e5498136646a0f75 14 | FileSystem 15 | <_TargetId>Folder 16 | 17 | -------------------------------------------------------------------------------- /Edge_tts_sharp/Properties/PublishProfiles/FolderProfile.pubxml.user: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | <<<<<<< HEAD 7 | 8 | True|2023-12-23T01:25:29.3607250Z;True|2023-12-23T09:25:04.4113739+08:00;True|2023-12-23T09:09:21.0346996+08:00; 9 | 10 | 11 | 12 | ======= 13 | 14 | >>>>>>> d90d7c952b02e74516234963e5498136646a0f75 15 | -------------------------------------------------------------------------------- /Edge_tts_sharp/Properties/PublishProfiles/FolderProfile1.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Release\netstandard2.0\publish\ 10 | FileSystem 11 | <_TargetId>Folder 12 | 13 | -------------------------------------------------------------------------------- /Edge_tts_sharp/Properties/PublishProfiles/FolderProfile1.pubxml.user: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | True|2024-03-18T07:28:25.4358299Z; 8 | 9 | 10 | -------------------------------------------------------------------------------- /Edge_tts_sharp/Tools.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Text.Json; 7 | 8 | namespace Edge_tts_sharp 9 | { 10 | public class Tools 11 | { 12 | public static T StringToJson(string json) 13 | { 14 | return JsonSerializer.Deserialize(json); 15 | } 16 | /// 17 | /// 获取嵌入文本资源,程序集.目录名.文件名(而不是\) 18 | /// 19 | /// 20 | /// 21 | public static string GetEmbedText(string res) 22 | { 23 | string result = string.Empty; 24 | 25 | try 26 | { 27 | Assembly assembly = Assembly.GetExecutingAssembly(); 28 | using (Stream stream = assembly.GetManifestResourceStream(res)) 29 | { 30 | if (stream != null) 31 | { 32 | using (StreamReader reader = new StreamReader(stream)) 33 | { 34 | result = reader.ReadToEnd(); 35 | } 36 | } 37 | else 38 | { 39 | result = string.Empty; 40 | } 41 | } 42 | 43 | } 44 | catch (Exception ex) 45 | { 46 | throw ex; 47 | } 48 | return result; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Edge_tts_sharp/Utils/Audio.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using NAudio; 9 | using NAudio.CoreAudioApi; 10 | using NAudio.Wave; 11 | using NAudio.Wave.SampleProviders; 12 | 13 | namespace Edge_tts_sharp.Utils 14 | { 15 | public static class Audio 16 | { 17 | public static async Task PlayToStreamAsync(Stream source, float volume = 1f, float speed = 0f, CancellationToken cancellationToken = default(CancellationToken)) 18 | { 19 | StreamMediaFoundationReader sr = new StreamMediaFoundationReader(source); 20 | SmbPitchShiftingSampleProvider smbPitchShiftingSampleProvider = new SmbPitchShiftingSampleProvider(sr.ToSampleProvider()); 21 | smbPitchShiftingSampleProvider.PitchFactor = (float)Math.Pow(2.0, (double)speed / 100.0); 22 | 23 | VolumeSampleProvider volumeProvider = new VolumeSampleProvider(smbPitchShiftingSampleProvider); 24 | volumeProvider.Volume = volume; 25 | 26 | DirectSoundOut directSoundOut = new DirectSoundOut(); 27 | directSoundOut.Init(volumeProvider.ToWaveProvider()); 28 | directSoundOut.Play(); 29 | 30 | while (directSoundOut.PlaybackState == PlaybackState.Playing) 31 | { 32 | if (cancellationToken.IsCancellationRequested) 33 | { 34 | directSoundOut.Stop(); 35 | break; 36 | } 37 | 38 | await Task.Delay(1000, cancellationToken); 39 | } 40 | } 41 | 42 | public static async Task PlayToByteAsync(byte[] source, float volume = 1.0f, float speed = 0.0f, CancellationToken cancellationToken = default) 43 | { 44 | using (var ms = new MemoryStream(source)) 45 | { 46 | ms.Position = 0; 47 | await PlayToStreamAsync(ms, volume, speed, cancellationToken); 48 | } 49 | } 50 | 51 | public static async Task PlayAudioAsync(string audioPath, float volume = 1.0f, float speed = 0.0f, CancellationToken cancellationToken = default) 52 | { 53 | using (var audioFile = new AudioFileReader(audioPath)) 54 | { 55 | var sampleProvider = audioFile.ToSampleProvider(); 56 | var pitchShiftingProvider = new SmbPitchShiftingSampleProvider(sampleProvider); 57 | using (var directSoundOut = new DirectSoundOut()) 58 | { 59 | pitchShiftingProvider.PitchFactor = (float)Math.Pow(2.0, speed / 100.0); 60 | var waveProvider = pitchShiftingProvider.ToWaveProvider(); 61 | directSoundOut.Init(waveProvider); 62 | directSoundOut.Volume = volume; 63 | directSoundOut.Play(); 64 | 65 | while (directSoundOut.PlaybackState == PlaybackState.Playing) 66 | { 67 | if (cancellationToken.IsCancellationRequested) 68 | { 69 | directSoundOut.Stop(); 70 | break; 71 | } 72 | await Task.Delay(1000, cancellationToken); 73 | } 74 | } 75 | } 76 | } 77 | 78 | public static async Task PlayAudioFromUrlAsync(string url, float volume = 1.0f, float speed = 0.0f, CancellationToken cancellationToken = default) 79 | { 80 | using (HttpClient client = new HttpClient()) 81 | { 82 | using (var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken)) 83 | { 84 | var stream = await response.Content.ReadAsByteArrayAsync(); 85 | await PlayToByteAsync(stream, volume, speed, cancellationToken); 86 | } 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Edge_tts_sharp/Utils/AudioPlayer.cs: -------------------------------------------------------------------------------- 1 | using NAudio.Wave; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Edge_tts_sharp.Utils 10 | { 11 | public class AudioPlayer 12 | { 13 | private WaveOutEvent waveOut; 14 | private WaveStream streamReader; 15 | private bool isPaused; 16 | private long pausedPosition; // 记录暂停时的位置 17 | public AudioPlayer(byte[] source, float volume = 1.0f) 18 | { 19 | var ms = new MemoryStream(source); 20 | streamReader = new StreamMediaFoundationReader(ms); 21 | waveOut = new WaveOutEvent(); 22 | waveOut.Init(streamReader); 23 | waveOut.Volume = volume; 24 | } 25 | public AudioPlayer(string path, float volume = 1.0f) 26 | { 27 | streamReader = new AudioFileReader(path); 28 | waveOut = new WaveOutEvent(); 29 | waveOut.Init(streamReader); 30 | waveOut.Volume = volume; 31 | } 32 | 33 | /// 34 | /// 播放音频 35 | /// 36 | public void Play() 37 | { 38 | if (isPaused) 39 | { 40 | // 从暂停的位置继续播放 41 | streamReader.Position = pausedPosition; 42 | isPaused = false; 43 | } 44 | else 45 | { 46 | waveOut.Play(); 47 | } 48 | 49 | while (waveOut.PlaybackState == PlaybackState.Playing) 50 | { 51 | Thread.Sleep(50); 52 | } 53 | } 54 | /// 55 | /// 播放音频 56 | /// 57 | public async Task PlayAsync() 58 | { 59 | if (isPaused) 60 | { 61 | // 从暂停的位置继续播放 62 | streamReader.Position = pausedPosition; 63 | isPaused = false; 64 | } 65 | waveOut.Play(); 66 | 67 | while (waveOut.PlaybackState == PlaybackState.Playing) 68 | { 69 | await Task.Delay(50); 70 | } 71 | } 72 | /// 73 | /// 暂停播放 74 | /// 75 | public void Pause() 76 | { 77 | if (!isPaused) 78 | { 79 | waveOut.Pause(); 80 | isPaused = true; 81 | // 记录暂停时的位置 82 | pausedPosition = streamReader.Position; 83 | } 84 | } 85 | /// 86 | /// 重新播放 87 | /// 88 | public void Resume() 89 | { 90 | Stop(); 91 | Play(); 92 | 93 | } 94 | /// 95 | /// 停止播放 96 | /// 97 | public void Stop() 98 | { 99 | waveOut.Stop(); 100 | pausedPosition = 0; 101 | isPaused = false; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Edge_tts_sharp/Utils/AudioStreamer.cs: -------------------------------------------------------------------------------- 1 | using NAudio.Wave; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Text; 6 | 7 | namespace Edge_tts_sharp.Utils 8 | { 9 | public class Mp3AudioStreamer 10 | { 11 | private BufferedWaveProvider _bufferedWaveProvider; 12 | private WaveOutEvent _waveOut; 13 | 14 | public Mp3AudioStreamer() 15 | { 16 | // 设定音频格式,确保与解码后的PCM数据格式一致 17 | _bufferedWaveProvider = new BufferedWaveProvider(new WaveFormat(44100, 16, 2)); 18 | _bufferedWaveProvider.BufferLength = 1024 * 1024; // 设置1MB缓冲区 19 | _bufferedWaveProvider.DiscardOnBufferOverflow = true; // 避免缓冲区溢出 20 | 21 | _waveOut = new WaveOutEvent 22 | { 23 | DesiredLatency = 100 // 减少播放延迟 24 | }; 25 | _waveOut.Init(_bufferedWaveProvider); 26 | _waveOut.Play(); 27 | } 28 | 29 | // 处理WebSocket的音频数据 30 | public void OnAudioReceived(byte[] mp3Data) 31 | { 32 | // 将 MP3 数据写入临时文件 33 | string tempFilePath = Path.GetTempFileName() + ".mp3"; 34 | File.WriteAllBytes(tempFilePath, mp3Data); 35 | 36 | // 使用 MediaFoundationReader 解码临时文件 37 | using (var reader = new MediaFoundationReader(tempFilePath)) 38 | { 39 | var buffer = new byte[16384]; // 16KB 缓冲区 40 | int bytesRead; 41 | 42 | while ((bytesRead = reader.Read(buffer, 0, buffer.Length)) > 0) 43 | { 44 | _bufferedWaveProvider.AddSamples(buffer, 0, bytesRead); 45 | } 46 | } 47 | 48 | // 删除临时文件 49 | File.Delete(tempFilePath); 50 | } 51 | 52 | 53 | 54 | public void Stop() 55 | { 56 | _waveOut.Stop(); 57 | } 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /Edge_tts_sharp/Wss.cs: -------------------------------------------------------------------------------- 1 | using Edge_tts_sharp.Model; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using WebSocketSharp; 6 | 7 | namespace Edge_tts_sharp 8 | { 9 | public enum SslProtocolsHack 10 | { 11 | Tls = 192, 12 | Tls11 = 768, 13 | Tls12 = 3072 14 | } 15 | 16 | public class Wss 17 | { 18 | public WebSocket wss { get; set; } 19 | public event Action OnLog; 20 | public event EventHandler OnMessage; 21 | public event EventHandler OnColse; 22 | public string wssAddress { get; set; } 23 | public Wss(string url) 24 | { 25 | try 26 | { 27 | wssAddress = url; 28 | wss = new WebSocket(wssAddress); 29 | var sslProtocolHack = (System.Security.Authentication.SslProtocols)(SslProtocolsHack.Tls12 | SslProtocolsHack.Tls11 | SslProtocolsHack.Tls); 30 | wss.SslConfiguration.EnabledSslProtocols = sslProtocolHack; 31 | if (url.Contains("wss://")) 32 | { 33 | wss.SslConfiguration.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true; 34 | } 35 | wss.OnOpen += (sender, e) => { 36 | OnLog(new Log { level = level.info, msg = "WebSocket Open" }); 37 | }; 38 | wss.OnMessage += (sender, e) => OnMessage(sender, e); 39 | wss.OnClose += (sender, e) => 40 | { 41 | //TlsHandshakeFailure 42 | if (e.Code == 1015 && wss.SslConfiguration.EnabledSslProtocols != sslProtocolHack) 43 | { 44 | OnLog(new Log { level = level.error, msg = "ssl握手失败,正在尝试重新连接." }); 45 | wss.SslConfiguration.EnabledSslProtocols = sslProtocolHack; 46 | wss.Connect(); 47 | } 48 | else 49 | { 50 | OnColse(sender, e); 51 | } 52 | }; 53 | 54 | } 55 | catch (Exception e) 56 | { 57 | OnLog(new Log { level = level.error, msg = $"WebSocket Exception:{e}" }); 58 | throw e; 59 | } 60 | } 61 | public bool Run() 62 | { 63 | wss.Connect(); 64 | if (wss.IsAlive) 65 | { 66 | OnLog(new Log { level = level.info, msg = "WebSocket 连接成功." }); 67 | } 68 | if (wss.IsSecure) 69 | { 70 | OnLog(new Log { level = level.info, msg = "WebSocket 是安全的." }); 71 | } 72 | return wss.IsAlive; 73 | } 74 | public void Close() 75 | { 76 | wss.Close(); 77 | } 78 | public void Send(string msg) 79 | { 80 | wss.Send(msg); 81 | OnLog(new Log { level = level.info, msg = $"WebSocket send msg:{msg}" }); 82 | } 83 | public void SendByte(byte[] msg) 84 | { 85 | wss.Send(msg); 86 | OnLog(new Log { level = level.info, msg = "WebSocket send msg: binary" }); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Entity-Now/Edge_tts_sharp/ff46bfdc49dcf5287b6b6e0bffa2669792884220/Logo.ico -------------------------------------------------------------------------------- /MenuLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Entity-Now/Edge_tts_sharp/ff46bfdc49dcf5287b6b6e0bffa2669792884220/MenuLogo.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Edge_tts_sharp 2 | [Edge_tts_sharp](https://www.nuget.org/packages/Edge_tts_sharp),是一个免费的C#库,调用Microsoft Edge Text to Speech接口生成音频。 3 | 4 | ## install 5 | ```sh 6 | NuGet\Install-Package Edge_tts_sharp 7 | ``` 8 | ## 方法 9 | 10 | ### 全局对象 11 | | 参数 | 说明 | 12 | | --- | --- | 13 | | Edge_tts.Debug | 调试模式,为true则显示日志 | 14 | | Edge_tts.Await | 同步模式,为true会等待函数执行完毕 | 15 | 16 | ### Invoke/PlayText/SaveAudio方法 17 | | 参数 | 说明 | 18 | | --- | --- | 19 | | PlayOption | 参数配置 | 20 | | eVoice | 音源 | 21 | | Action> | 回调函数,参数是一个binary数组 | 22 | 23 | ### PlayOption对象 24 | | 名称 | 说明 | 25 | | --- | --- | 26 | | Text | 播放的文本 | 27 | | Rate | 播放速度,是一个-100至+100的数值 | 28 | | Volume | 音量,是一个0-1的浮点数值 | 29 | | SavePath | 音频保存路径,为空不保存 | 30 | 31 | ## 获取一个Player对象 32 | > **PlayerAudio**对象,支持对音频进行简单的控制,例如:开始、暂停、继续播放、停止播放等。 33 | ```cs 34 | // 获取一个PlayerAudio对象 35 | static void getPlayer(string msg, eVoice voice) 36 | { 37 | PlayOption option = new PlayOption 38 | { 39 | Rate = 0, 40 | Text = msg, 41 | }; 42 | var player = Edge_tts.GetPlayer(option, voice); 43 | 44 | Console.WriteLine("开始播放"); 45 | player.PlayAsync(); 46 | Thread.Sleep(3000); 47 | 48 | 49 | Console.WriteLine("暂停播放"); 50 | player.Pause(); 51 | Thread.Sleep(3000); 52 | 53 | Console.WriteLine("继续播放"); 54 | player.PlayAsync(); 55 | Thread.Sleep(5000); 56 | 57 | player.Stop(); 58 | Console.WriteLine("结束播放"); 59 | } 60 | ``` 61 | 62 | ## 文字转语言 63 | ```cs 64 | // 文本转语音 65 | static void TextToAudio() 66 | { 67 | PlayOption option = new PlayOption 68 | { 69 | Rate = 0, 70 | Text = "Hello EdgeTTs", 71 | }; 72 | var voice = Edge_tts.GetVoice().First(); 73 | Edge_tts.PlayText(option, voice); 74 | } 75 | ``` 76 | 77 | ## 保存到本地 78 | ```cs 79 | // 保存音频 80 | static void SaveAudio() 81 | { 82 | PlayOption option = new PlayOption 83 | { 84 | Rate = 0, 85 | Text = "Hello EdgeTTs", 86 | SavePath = "C:\\audio" 87 | }; 88 | // 获取xiaoxiao语音包 89 | var voice = Edge_tts.GetVoice().FirstOrDefault(i => i.Name == "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaoxiaoNeural)"); 90 | Edge_tts.SaveAudio(option, voice); 91 | } 92 | ``` 93 | 94 | ## 自定义操作 95 | ```cs 96 | // 自定义接口使用 97 | static void MyFunc(string msg, eVoice voice) 98 | { 99 | PlayOption option = new PlayOption 100 | { 101 | Rate = 0, 102 | Text = msg, 103 | }; 104 | Edge_tts.Invoke(option, voice, libaray => 105 | { 106 | // 写入自己的操作 107 | // ... 108 | } ); 109 | } 110 | ``` 111 | 112 | ## 获取音频列表 113 | ```cs 114 | using Edge_tts_sharp; 115 | 116 | var voices = Edge_tts.GetVoice(); 117 | foreach(var item in voices){ 118 | Console.WriteLine($"voice name is{item.Name}, locale(语言) is {item.Locale}, SuggestedCodec(音频类型) is {item.SuggestedCodec}"); 119 | } 120 | ``` 121 | ## 汉语语音包有: 122 | 123 | | ShortName | Locale | 地区 | 124 | |------------------------|--------------|--------------| 125 | | zh-HK-HiuGaaiNeural | zh-HK | 香港 | 126 | | zh-HK-HiuMaanNeural | zh-HK | 香港 | 127 | | zh-HK-WanLungNeural | zh-HK | 香港 | 128 | | zh-CN-XiaoxiaoNeural | zh-CN | 中国(大陆) | 129 | | zh-CN-XiaoyiNeural | zh-CN | 中国(大陆) | 130 | | zh-CN-YunjianNeural | zh-CN | 中国(大陆) | 131 | | zh-CN-YunxiNeural | zh-CN | 中国(大陆) | 132 | | zh-CN-YunxiaNeural | zh-CN | 中国(大陆) | 133 | | zh-CN-YunyangNeural | zh-CN | 中国(大陆) | 134 | | zh-CN-liaoning-XiaobeiNeural | zh-CN-liaoning | 中国(辽宁) | 135 | | zh-TW-HsiaoChenNeural | zh-TW | 台湾 | 136 | | zh-TW-YunJheNeural | zh-TW | 台湾 | 137 | | zh-TW-HsiaoYuNeural | zh-TW | 台湾 | 138 | | zh-CN-shaanxi-XiaoniNeural | zh-CN-shaanxi | 中国(陕西) | 139 | 140 | 141 | ## 更新内容 142 | 143 | - 2023.10.28 144 | - 第一次上传。 145 | - 2023.10.30 146 | - 更新调用接口的方式 147 | -------------------------------------------------------------------------------- /edge_tts_test/Program.cs: -------------------------------------------------------------------------------- 1 | // See https://aka.ms/new-console-template for more information 2 | using Edge_tts_sharp; 3 | using Edge_tts_sharp.Model; 4 | using Edge_tts_sharp.Utils; 5 | using System.Web; 6 | 7 | Edge_tts.Await = true; 8 | PlayOption option = new PlayOption 9 | { 10 | Rate = 1, 11 | Text = "" 12 | }; 13 | string msg = string.Empty; 14 | Console.WriteLine("请输入文本内容."); 15 | option.Text = Console.ReadLine(); 16 | // 获取xiaoxiao语音包 17 | var voice = Edge_tts.GetVoice().FirstOrDefault(i=> i.Name == "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaoxiaoNeural)"); 18 | // 文字转语音,并且设置语速 19 | Edge_tts.PlayText(option, voice); 20 | Console.WriteLine("自动输出"); 21 | Console.ReadLine(); 22 | 23 | 24 | // 保存音频 25 | static void SaveAudio() 26 | { 27 | PlayOption option = new PlayOption 28 | { 29 | Rate = 0, 30 | Text = "Hello EdgeTTs", 31 | SavePath = "C:\\audio" 32 | }; 33 | // 获取xiaoxiao语音包 34 | var voice = Edge_tts.GetVoice().FirstOrDefault(i => i.Name == "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaoxiaoNeural)"); 35 | Edge_tts.SaveAudio(option, voice); 36 | } 37 | // 文本转语音 38 | static void TextToAudio() 39 | { 40 | PlayOption option = new PlayOption 41 | { 42 | Rate = 0, 43 | Text = "Hello EdgeTTs", 44 | }; 45 | var voice = Edge_tts.GetVoice().First(); 46 | Edge_tts.PlayText(option, voice); 47 | } 48 | // 自定义接口使用 49 | static void MyFunc(string msg, eVoice voice) 50 | { 51 | PlayOption option = new PlayOption 52 | { 53 | Rate = 0, 54 | Text = msg, 55 | }; 56 | Edge_tts.Invoke(option, voice, libaray => 57 | { 58 | // 写入自己的操作 59 | // ... 60 | } ); 61 | } 62 | // 获取一个PlayerAudio对象 63 | static void getPlayer(string msg, eVoice voice) 64 | { 65 | PlayOption option = new PlayOption 66 | { 67 | Rate = 0, 68 | Text = msg, 69 | }; 70 | var player = Edge_tts.GetPlayer(option, voice); 71 | 72 | Console.WriteLine("开始播放"); 73 | player.PlayAsync(); 74 | Thread.Sleep(3000); 75 | 76 | 77 | Console.WriteLine("暂停播放"); 78 | player.Pause(); 79 | Thread.Sleep(3000); 80 | 81 | Console.WriteLine("继续播放"); 82 | player.PlayAsync(); 83 | Thread.Sleep(5000); 84 | 85 | player.Stop(); 86 | Console.WriteLine("结束播放"); 87 | } -------------------------------------------------------------------------------- /edge_tts_test/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Release\net8.0\publish\win-x64\ 10 | FileSystem 11 | <_TargetId>Folder 12 | net8.0 13 | win-x64 14 | true 15 | true 16 | true 17 | true 18 | 19 | -------------------------------------------------------------------------------- /edge_tts_test/Properties/PublishProfiles/FolderProfile.pubxml.user: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | False|2023-12-22T09:29:43.6139459Z;False|2023-12-22T17:29:14.6142435+08:00; 8 | 9 | 10 | -------------------------------------------------------------------------------- /edge_tts_test/edge_tts_test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0-windows7.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /edge_tts_test/edge_tts_test.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | <_LastSelectedProfileId>D:\Language\cs\Edge_tts_sharp\edge_tts_test\Properties\PublishProfiles\FolderProfile.pubxml 5 | 6 | --------------------------------------------------------------------------------