• 如何把采集的中文字幕转换成日语语音?
  • 发布于 1个月前
  • 51 热度
    0 评论

最近有个需求,就是把我用中文录制的视频转成日语。基本思路是:中文视频->采集中文字幕->优化->转成日语->转成日语语音->合成到视频中。当前很多软件具有这个功能,一是收费,二是正好实现一下,把代码保留在自己手中,方便集成。


下面是直接看代码吧!
smartfill.srt
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; }
}
以上就是实现的基本逻辑。当然,质量还需要提升优化,这都是细活了,这里就不再继续了。
用户评论