最近有个需求,就是把我用中文录制的视频转成日语。基本思路是:中文视频->采集中文字幕->优化->转成日语->转成日语语音->合成到视频中。当前很多软件具有这个功能,一是收费,二是正好实现一下,把代码保留在自己手中,方便集成。
1 00:00:02,912 --> 00:00:05,905 こんにちは、今日は皆さんに紹介します。 2 00:00:05,905 --> 00:00:08,412 一つの生成型AIに基づいた 3 00:00:08,412 --> 00:00:11,162 インテリジェントな補助ツール、 4 00:00:11,162 --> 00:00:13,913 入力ツール、smart fillです。 5 00:00:13,913 --> 00:00:16,663 smart fill は、私が開発したツールで、 6 00:00:17,391 --> 00:00:20,060 生成型AIをベースにして、 7 00:00:20,303 --> 00:00:23,295 大規模言語モデルを駆動基盤とした 8 00:00:23,295 --> 00:00:26,288 Chrome拡張機能を使った、 9 00:00:26,612 --> 00:00:29,605 インテリジェントな入力ツールです。 10 00:00:29,605 --> 00:00:32,516 smart fill は、ユーザーが提供する 11 00:00:32,516 --> 00:00:35,509 情報を抽出して、要約、計算、分類、 12 00:00:35,711 --> 00:00:38,421 変換することができます。 13 00:00:38,583 --> 00:00:41,171 これにより、迅速かつ正確にユーザーを 14 00:00:41,495 --> 00:00:44,488 サポートし、これらの情報をフォームに 15 00:00:44,488 --> 00:00:47,400 自動的に入力します。現在、 16 00:00:47,400 --> 00:00:50,231 smart fill はテキストの自動入力機能を 17 00:00:50,231 --> 00:00:53,223 提供しており、私たちが文章を書いて、 18 00:00:53,223 --> 00:00:55,650 拡張機能に入力すると、 19 00:00:55,974 --> 00:00:58,805 自動的にこのページに 20 00:00:58,805 --> 00:01:01,797 入力されます。このデモのように。 21 00:01:01,797 --> 00:01:04,709 さらに、様々なファイルのアップロードも 22 00:01:04,709 --> 00:01:07,379 サポートしており、画像やWord、PDF、 23 00:01:07,621 --> 00:01:10,533 Excel、TXTなど 24 00:01:10,533 --> 00:01:13,122 テキストファイルをアップロードして、 25 00:01:13,445 --> 00:01:16,276 要約と分類を行い、 26 00:01:16,438 --> 00:01:18,137 フォームに自動的に 27 00:01:19,876 --> 00:01:22,788 入力される機能を提供しています。 28 00:01:22,788 --> 00:01:25,578 また、リアルタイム音声機能も提供します。 29 00:01:25,902 --> 00:01:28,571 私たちが話している間に、 30 00:01:28,571 --> 00:01:31,564 smart fill が情報を収集し、 31 00:01:31,564 --> 00:01:34,557 それをテキストに変換し、最終的に 32 00:01:34,557 --> 00:01:37,468 送信されると、フォームに自動的に入力されます。 33 00:01:37,468 --> 00:01:40,461 同時に、スマホのリモート送信も 34 00:01:40,461 --> 00:01:43,373 サポートしています。スマホでQRコードを 35 00:01:43,373 --> 00:01:46,123 スキャンして、スマホのカメラや 36 00:01:46,528 --> 00:01:49,318 マイクを使って情報を 37 00:01:49,318 --> 00:01:51,866 収集し、それをテキストに 38 00:01:51,866 --> 00:01:54,778 変換して、フォームに自動的に 39 00:01:54,778 --> 00:01:57,528 入力することができます。 40 00:01:59,874 --> 00:02:01,896 さて、smart fill に関する 41 00:02:02,624 --> 00:02:05,536 紹介については、こちらのサイトで 42 00:02:05,779 --> 00:02:08,691 さらに詳しい情報を確認できます。实现代码,这里用了Microsoft的Speech,当然也是收费的,至少手边有,不用重新购买。代码基本思路是把.srt转成Speech识别的SSML,然后交给Sppeck API就可以了。
using System.Text; using System.Text.RegularExpressions; using Microsoft.CognitiveServices.Speech; string subscriptionKey = File.ReadAllText("C:/GPT/audiokey.txt"); string region = "westus2"; var config = SpeechConfig.FromSubscription(subscriptionKey, region); var voceName = "ja-JP-KeitaNeural"; config.SpeechSynthesisVoiceName = voceName; using var synthesizer = new SpeechSynthesizer(config, null); synthesizer.SynthesisStarted += (s, e) => { // 堆代码 duidaima.com Console.WriteLine($"开始生成音频……"); }; synthesizer.SynthesisCompleted += (s, e) => { Console.WriteLine($"生成语音完成!"); }; var subtitles = ParseSrt(Directory.GetCurrentDirectory() + "/smartfill.srt"); var voices = ParseVoices(subtitles); var ssml = BuildSSML(voices); using (var result = await synthesizer.SpeakSsmlAsync(ssml.ToString())) { if (result.Reason == ResultReason.SynthesizingAudioCompleted) { using (var outputStream = new FileStream("smartfill.wav", FileMode.Create)) { using (var waveFileWriter = new NAudio.Wave.WaveFileWriter(outputStream, new NAudio.Wave.WaveFormat(16000, 16, 1))) { waveFileWriter.Write(result.AudioData, 0, result.AudioData.Length); } } } else { Console.WriteLine($"语音合成失败:{result.Reason}"); } } string BuildSSML(List<VoiceItem> voices) { var ssmlBuilder = new StringBuilder($@"<speak version=""1.0"" xmlns=""http://www.w3.org/2001/10/synthesis"" xmlns:mstts=""http://www.w3.org/2001/mstts"" xml:lang=""en-US"">"); foreach (var voice in voices) { if (string.IsNullOrWhiteSpace(voice.Text)) { ssmlBuilder.AppendLine(@$"<voice name=""{voceName}""><mstts:silence type=""comma-exact"" value=""{voice.TotalMillisecond}ms""/>,</voice>"); } else { ssmlBuilder.AppendLine(@$"<voice name=""{voceName}""><mstts:audioduration value=""{voice.TotalMillisecond}ms""/> <mstts:express-as style=""advertisement_upbeat"" styledegree=""2"">{voice.Text} </mstts:express-as></voice>"); } } ssmlBuilder.AppendLine("</speak>"); return ssmlBuilder.ToString(); } List<VoiceItem> ParseVoices(List<SubtitleItem> subtitles) { var voices = new List<VoiceItem>(); if (subtitles[0].StartTime != TimeSpan.Zero) { var voice = new VoiceItem(); voice.Text = ""; voice.TotalMillisecond = (long)(subtitles[0].StartTime - TimeSpan.Zero).TotalMilliseconds; voices.Add(voice); } var newVoice = new VoiceItem(); var starTS = subtitles[0].StartTime; newVoice.Text += subtitles[0].Text.Replace(" ", ""); var endTS = TimeSpan.Zero; for (var i = 0; i < subtitles.Count - 1; i++) { var subtitle1 = subtitles[i]; var subtitle2 = subtitles[i + 1]; if (subtitle1.EndTime == subtitle2.StartTime) { endTS = subtitle2.EndTime; newVoice.Text += subtitle2.Text.Replace(" ", ""); } else { endTS = subtitle1.EndTime; newVoice.TotalMillisecond += (long)(endTS - starTS).TotalMilliseconds; voices.Add(newVoice); voices.Add(new VoiceItem { Text = "", TotalMillisecond = (long)(subtitle2.StartTime - subtitle1.EndTime).TotalMilliseconds }); newVoice = new VoiceItem(); newVoice.Text = subtitle2.Text.Replace(" ", ""); starTS = subtitle2.StartTime; } } endTS = subtitles[subtitles.Count - 1].EndTime; newVoice.TotalMillisecond += (long)(endTS - starTS).TotalMilliseconds; voices.Add(newVoice); return voices; } List<SubtitleItem> ParseSrt(string filePath) { var subtitles = new List<SubtitleItem>(); var regex = new Regex( @"^\s*(\d+)\s*(?:\r\n|\n|\r) (?<start>\d{2}:\d{2}:\d{2},\d{3})\s*-->?\s* (?<end>\d{2}:\d{2}:\d{2},\d{3})(?:\r\n|\n|\r) (?<text>.*?)(?=(?:\r\n\r\n|\n\n|\r\r|\z))", RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace); var srtContent = File.ReadAllText(filePath); foreach (Match match in regex.Matches(srtContent)) { var subtitle = new SubtitleItem { StartTime = TimeSpan.Parse(match.Groups["start"].Value.Replace(",", ".")), EndTime = TimeSpan.Parse(match.Groups["end"].Value.Replace(",", ".")), Text = match.Groups["text"].Value.Replace("\r\n", " ").Replace("\n", " ").Replace(" ", "") }; subtitles.Add(subtitle); } return subtitles; } public class VoiceItem { public long TotalMillisecond { get; set; } public string Text { get; set; } } public class SubtitleItem { public TimeSpan StartTime { get; set; } public TimeSpan EndTime { get; set; } public string Text { get; set; } }以上就是实现的基本逻辑。当然,质量还需要提升优化,这都是细活了,这里就不再继续了。