🎞️

【C#】合成音声ソフトのタイミング情報ファイル(.lab)から字幕ファイル(.srt)をつくる

2023/12/09に公開

こんにちは、いぬいぬです!(これは本物です!

この記事は「ぼすきー アドカレ3 Advent Calendar 2023」の9日目の記事です。
昨日の記事は山田結城@羽子さんの「MisskeyのプラグインAPIとそのリスクの理解」でした。
https://adventar.org/calendars/9767

またこの記事は、「ぼすきー 裏 Advent Calendar 2023」の「【深入り解説】タイミング情報ファイル(.lab)って?」の記事の続きでもあります!

https://note.com/inuinu_/n/n6b01520b39c2

上の記事では「タイミング情報ファイル」について深く説明しました。
この記事ではdotnet/C#のライブラリを使って、「タイミング情報ファイル」(.lab)からYouTubeとかの字幕に使える字幕ファイル(.srt)を作ってみたいと思います!

やること!

  • タイミング情報ファイルを書き出せる合成音声ソフトからタイミング情報ファイルを書き出す
    • 今回はソング系の歌声合成音声ソフトのタイミング情報ファイルを使います
    • トーク系の合成音声ソフト(ソフトウェアトークソフト)の場合はもう字幕ファイル出力に対応していることが多いので…

前準備

  • .NET SDKのインストール
  • 今回は.NET SDK 8 / C# 12でやります
  • 適当なテキストエディタ(VSCode辺りがおすすめ!)
  • git
  • タイミング情報ファイル(.lab)を書き出せる歌声合成音声ソフト

早速作るぞ!

最初のテンプレの準備

適当なフォルダを用意し(例:LabToJimaku
まずコンソールで以下のコマンドを叩いてテンプレを用意します。

$ dotnet new console -n LabToJimaku
$ cd  .\LabToJimaku\
$ dotnet new gitignore

そのままdotnet runするとHello, World!って出たらOK!!

$ dotnet run
Hello, World!

ライブラリを参照する

今回は LibSasara というライブラリを使います。

https://inuinu2022.github.io/LibSasara/

nuget.orgからはDLできない[1]ので、git submoduleを使います。

$ cd ..
$ git submodule add https://github.com/InuInu2022/LibSasara.git

最新のタグのバージョンに変更しておきます(ここではv0.2.4)[2]

$ cd ./LibSasara/
$ git checkout v0.2.4

ライブラリへの参照を追加します。

$ cd ..
$ dotnet new sln
$ dotnet sln add .\LabToJimaku\LabToJimaku.csproj
$ dotnet sln add .\LibSasara\LibSasara\LibSasara.csproj
$ dotnet add .\LabToJimaku\LabToJimaku.csproj reference .\LibSasara\LibSasara\LibSasara.csproj

「参照 LibSasara\LibSasara\LibSasara.csproj がプロジェクトに追加されま
した。」と出たら成功です。

最後にdotnet restoreしてライブラリの準備は終了です。

$ dotnet restore

プログラムを書くぞ~!

プログラムを書く:labファイルを読み込む

テキストエディタでProgram.csを開いて中身を全部消します。

次に1行目にLibSasaraを使うよ、の意味でusingディレクティブを書きます。

using LibSasara;

タイミング情報ファイルを読み込むので、
コマンドライン引数でタイミング情報ファイル(.lab)へのパスを指定するようにしてみましょう!

//コマンドライン引数の一つ目はlabファイルへのパス
var labPath = args[0];

//Labファイル読み込み
var lab = await SasaraLabel.LoadAsync(labPath);

Console.WriteLine(lab.ToString());

テキトーに.labファイルを音声合成ソフトから書き出して、指定してみましょう。

$ dotnet run path/to/example.lab

コンソールにlabファイルの中身が出力されたらOKです!

プログラムを書く:解析して字幕形式に変換

解析してみましょう…
といってもSasaraLabel.LoadAsync()でもう解析済みです!

なので字幕形式に変換してみます。

YouTubeで使える字幕形式は色々あります!

https://support.google.com/drive/answer/1372218#zippy=%2C基本的なファイル形式

今回は SubRip(.srt) 形式にします。

1
00:02:16,612 --> 00:02:19,376
Senator, we're making
our final approach into Coruscant.

2
00:02:19,482 --> 00:02:21,609
Very good, Lieutenant.

https://en.wikipedia.org/wiki/SubRip#SubRip_text_file_format

字幕に変換するには全部つながったタイミング情報をセリフやフレーズ毎に分割して、
それぞれの時間を指定してあげる必要がありますね…。

なんと!それにピッタリの機能があります!
SplitToSentence()を使うとセリフや小節やフレーズ単位で分割できます。

https://inuinu2022.github.io/LibSasara/api/LibSasara.Model.Lab.html#LibSasara_Model_Lab_SplitToSentence_System_Double_

var output = lab
	//小節・フレーズ単位で分割(0.1秒空白毎)
	.SplitToSentence(0.1)

必要な情報だけ抜き出しましょう!
必要なのは、開始時間、終了時間、そして字幕のセリフ(フレーズ)です。
タプルのリストに変換しちゃいましょう。

ついでに時間も秒に変換します。labファイルの時間表現は1000万分の1秒なので1000万倍すれば秒単位になります。
セリフは、リストを音素だけのリストに変換してstring.Join()で一つの文字列にします。

	//必要な情報だけ抜き出してリスト化
	.Select(v => (
		Start: v[0].From / 10000000,	//開始
		End: v[^1].To / 10000000,	//終了
		Phrase: string.Join("",v.Select(s=> s.Phoneme))	//音素文字列
	))

必要な情報のリストから、srtのフォーマットに変換します。

	//字幕(.srt)フォーマットに変換
	.Select((v,i)=> $"""
{i+1}
{TimeSpan.FromSeconds(v.Start).ToString(@"hh\:mm\:ss\,fff")} --> {TimeSpan.FromSeconds(v.End).ToString(@"hh\:mm\:ss\,fff")}
{v.Phrase}

"""
	);

TimeSpan.FromSeconds()TimeSpan型に変換したあとToString()時にフォーマット指定することで、srtの時間表現に変換できます。
srtの時間表現に合うフォーマットはhh\:mm\:ss\,fffです。

結果をテキストファイルとして保存しましょう。

//labファイルと同じ名前で拡張子を.srtに変換して保存
await File.WriteAllLinesAsync(
	Path.ChangeExtension(labPath, "srt"),
	output
);

コンソールに出力するのはもー要らないので消しておきます。
(念のため残してもいいよ)

- Console.WriteLine(lab.ToString());
+ //Console.WriteLine(lab.ToString());

プログラムを書く:カナ変換する

このままでは字幕が全部ローマ字です。
オラーっ!って頑張って気合で全部書き換えてもいいですが、せめてひらがなに変換しましょう。

アルファベット→かな変換には WanaKana-net を使います。

https://github.com/MartinZikmund/WanaKana-net

$ dotnet add package WanaKana-net

変換する処理を途中に挟んでみます!

冒頭にusing追加
using WanaKanaNet;
必要なタプルを抜き出した後に追加挿入
	//音素 -> ひらがな変換
	.Select(v => (
		Start: v.Start,
		End: v.End,
		Phrase: WanaKana.ToHiragana(v.Phrase)
	))
出力
1
00:00:23,185 --> 00:00:25,780
naNdemonaitokuchiotsuguNda

2
00:00:28,210 --> 00:00:30,805
hoNtowachocltoashiotometakute

3
00:00:33,245 --> 00:00:37,400
dakedomokimiwahayaashidesucltomaeoyukukara

1
00:00:23,185 --> 00:00:25,780
なんでもないとくちおつぐんだ

2
00:00:28,210 --> 00:00:30,805
ほんとわちょcltおあしおとめたくて

3
00:00:33,245 --> 00:00:37,400
だけどもきみわはやあしですcltおまえおゆくから

やったー!ひらがなになった!
あれ?…でも上手く変換できてないところがある…

タイミング情報ファイルの音素表現はローマ字とは異なるのでそのままではうまくいきません。
そこで追加の変換ルールを指定します。

プログラムを書く:かな変換を調整する

追加の変換ルールを指定します!!!
やったる!!!

//音素からカナ変換の特別ルール
WanaKanaOptions kanaOption = new()
{
	CustomKanaMapping = new Dictionary<string, string>()
	{
		{"cl","っ"},
		{"di","でぃ"}
	}
};
ToHiragana()の引数でオプション指定する
+		Phrase: WanaKana.ToHiragana(v.Phrase, kanaOption)
- 		Phrase: WanaKana.ToHiragana(v.Phrase)
出力
1
00:00:23,185 --> 00:00:25,780
なんでもないとくちおつぐんだ

2
00:00:28,210 --> 00:00:30,805
ほんとわちょっとあしおとめたくて

3
00:00:33,245 --> 00:00:37,400
だけどもきみわはやあしですっとまえおゆくから

やったー!!!うまくいきました。

制限もあります!

  • 「は」「を」は「わ」「お」になる
  • 英語は無理

参考動画

出力された字幕を動画と一緒に見るとこんな感じです!
字幕をONにしてみてね!

https://youtu.be/rLQioq6LdzA?cc_lang_pref=ja&cc_load_policy=1

アプリ化する

dotnet build

まずはdotnet buildしてみます。

$ dotnet build -c Release

これで/bin/Release/net8.0/以下に実行ファイル(Windowsならexe)がアプリとして出力されました。

この中身をzipとかでまとめて配布してもいいんですが、

  • ファイルが多い
  • (Windowsでビルドしたときは)Windows限定
  • .NETランタイムをインストールしてもらわないと動かない
  • まいかいzipとかで圧縮するのが手間

と色々メンドー!!!です。

dotnet publish

メンドーなのを改善します!!!
C#/dotnetではアプリとして出力するのはdotnet publishコマンドを使います。

LabToJimaku.csprojファイルを開いて以下の3行を書きましょう。

LabToJimaku.csproj
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
+   <PublishSingleFile>true</PublishSingleFile>
+   <DebugSymbols Condition="'$(Configuration)' == 'Release'">false</DebugSymbols>
+   <DebugType Condition="'$(Configuration)' == 'Release'">none</DebugType>
  </PropertyGroup>

dotnet buildじゃなくてdotnet publishコマンドを使います。

$ dotnet publish

これを実行すると、/bin/Release/net8.0/win-x64/publish/以下に実行ファイル(WindowsならLabToJimaku.exe)ができます。

次にpublishするときに、実行ファイルをわかりやすいところにコピーし、自動でzip圧縮するように設定します。

LabToJimaku.csproj
  <Target Name="MakeZipPackage" AfterTargets="Publish">
    <MakeDir Directories="./publish/" />
    <ZipDirectory SourceDirectory="$(OutputPath)/publish/" DestinationFile="./publish/$(AssemblyName)-$(RuntimeIdentifier)-v.$(Version).zip" Overwrite="true" />
    <Message Text="Actions After Publish" Importance="high" />
  </Target>

-rオプションでWindows以外でも動くようにpublishします。

$ dotnet publish -r win-x64
$ dotnet publish -r osx-arm64
$ dotnet publish -r osx-x64
$ dotnet publish -r linux-x64

これで/publish/以下にどれでも動く実行ファイルができます!やったね!

もっと拡張するなら

今回のソースは以下にあります。
https://github.com/InuInu2022/LabToJimaku

色々拡張するのも面白いですね!

  • 時間のオフセット対応
    • 動画で冒頭に間を入れてる時とか
    • トーク用のセリフはそもそも全部この対応がいる
  • 漢字変換
    • OSのIMEの機能を呼び出す方法がある
    • そこそこ大変
    • OSごとに処理を書く必要がある
  • 英語のカタカナ化
    • パターンを全部書けばできなくもないかも
    • 前回の記事で紹介した音素表PDFにパターンが載ってます
  • ちゃんとしたアプリにする
    • ConsoleAppFrameworkとかつかってちゃんとしたコマンドラインアプリに!
    • UIをつけるならAvalonia, Blazer, MAUIあたりがおすすめ

アドカレ

次の記事は遊牧家族/yuubokuさんの「リアクションデッキのカスタム絵文字を全部ノートに書く方法(ぼすきーアドカレ3参加記事)」です!

https://adventar.org/calendars/9767

脚注
  1. .nupkgファイルはあるのでローカルnugetインストールなら可能 ↩︎

  2. 安定版を使うため ↩︎

Discussion