闽公网安备 35020302035485号
最近有个需求,就是把我用中文录制的视频转成日语。基本思路是:中文视频->采集中文字幕->优化->转成日语->转成日语语音->合成到视频中。当前很多软件具有这个功能,一是收费,二是正好实现一下,把代码保留在自己手中,方便集成。
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; }
}
以上就是实现的基本逻辑。当然,质量还需要提升优化,这都是细活了,这里就不再继续了。