├── .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 |
--------------------------------------------------------------------------------