資料の対話型音声の生成
NotebookLMを使って、要約した資料から対話型の音声データを生成してみました。生成される音声データは英語のみなので、文字起こし⇒日本語訳⇒音声合成までやってみました。
NotebookLMで英語対話音声の生成
解説してほしいWebページやPDFをソースとして読み込んで、下の画像のように「ノートブックガイド」を開いて、「音声の概要」で英語の対話音声を生成する。音声の生成が完了するまで、結構な時間がかかりました。
音声が生成されたら、以降の処理をするためにダウンロードします。
この音声ファイルから日本語の対話型の音声を作ることが目標です。
WhisperXで文字起こし
次は、英語の音声を文字起こしします。対話型の音声なので、文字起こしする際には話者を識別してテキストを話者ごとに分離する必要があります。
この処理はWhisperXというライブラリを使用しました。
以下はそのコード。
import os
import json
import whisperx
from dotenv import load_dotenv
load_dotenv(verbose=True)
device = "cpu"
audio_file = "data/sound.wav"
batch_size = 16
compute_type = "float32"
hf_token = os.getenv('HF_TOKEN')
if __name__=="__main__":
model = whisperx.load_model("distil-medium.en", device, compute_type=compute_type)
audio = whisperx.load_audio(audio_file)
result = model.transcribe(audio, batch_size=batch_size)
print(result["segments"])
model_a, metadata = whisperx.load_align_model(language_code=result["language"], device=device)
result = whisperx.align(result["segments"], model_a, metadata, audio, device, return_char_alignments=False)
print(result["segments"])
diarize_model = whisperx.DiarizationPipeline(use_auth_token=hf_token, device=device)
diarize_segments = diarize_model(audio, min_speakers=2, max_speakers=2)
result = whisperx.assign_word_speakers(diarize_segments, result)
print(diarize_segments)
with open("data/segments.json", 'w') as f:
json.dump(result, f, indent=4)
翻訳
音声をテキストに書き起こせたので、次は翻訳です。翻訳はollamaに登録されているモデルを使用します。
使用したコードは以下。
import json
import ollama
from tqdm import tqdm
DEFAULT_SYSTEM_PROMPT = "あなたは優秀な日本人のラジオのシナリオ編集者です。常に日本語で回答してください。"
instruction = """
以下に英語の対話テキストの一部が与えられます。ラジオのパーソナリティとゲストが話しているような自然な対話でわかりやすく、流暢な日本語に翻訳してください。
制約条件:
- 意訳してもOKですが、フォーマットは崩さないでください。
- アルファベットを使わず、カタカナで表記してください。
- 意味不明な場合は、前後の対話の意味が通るように修正してください。
- 正しい日本語文法で翻訳してください。
- すべての文を1文1文、翻訳してください。
- 翻訳文以外のテキストを生成しないでください。
- テキストのフォーマットを変更しないでください。
- SPEAKERは翻訳後も明記してください。
### 英語のテキスト
{text}
### 日本語のテキスト
"""
model = 'dsasai/llama3-elyza-jp-8b'
def transrate(text):
response = ollama.chat(model=model, messages=[
{
'role': 'system',
'content': DEFAULT_SYSTEM_PROMPT,
},
{
'role': 'user',
'content': instruction.format(text=text),
},
])
return response['message']['content']
if __name__=="__main__":
filename = 'data/segments.json'
data = json.load(open(filename, 'r'))
texts, speaker = [], 'SPEAKER_01'
text = ''
for seg in tqdm(data['segments']):
y = seg['text'].replace('\n', '').strip()
x = f"{seg['speaker']}: {y} \n"
text += x
if len(text) > 512:
texts.append(text)
text = ''
results = []
for text in texts:
print(text)
ret = transrate(text)
results.append({
'text': ret,
'raw': text,
'speaker': speaker
})
print(ret)
json.dump(results, open('./data/segments_ja.json', 'w', encoding='utf8'), indent=4, ensure_ascii=False)
日本語音声合成
日本語テキストが生成されたら、最後に音声合成します。音声合成には以下のライブラリを使用しました。
使用したコードは以下。
from style_bert_vits2.nlp import bert_models
from style_bert_vits2.constants import Languages
from pathlib import Path
from style_bert_vits2.tts_model import TTSModel
from scipy.io.wavfile import write as write_wav
import json
bert_models.load_model(Languages.JP, "ku-nlp/deberta-v2-large-japanese-char-wwm")
bert_models.load_tokenizer(Languages.JP, "ku-nlp/deberta-v2-large-japanese-char-wwm")
#モデルの重みが格納されているpath
font = {
"SPEAKER_01": {
"model_file": "jvnv-F2-jp/jvnv-F2_e166_s20000.safetensors",
"config_file": "jvnv-F2-jp/config.json",
"style_file": "jvnv-F2-jp/style_vectors.npy",
'style': "Happy"
},
"SPEAKER_00": {
"model_file": "jvnv-M1-jp/jvnv-M1-jp_e158_s14000.safetensors",
"config_file": "jvnv-M1-jp/config.json",
"style_file": "jvnv-M1-jp/style_vectors.npy",
"style": "Happy"
}
}
assets_root = Path("model_assets")
if __name__=="__main__":
segments = json.load(open('../data/notebooklm/segments_ja.json', 'r', encoding='utf8'))
for k, v in font.items():
model = TTSModel(
model_path=assets_root / v['model_file'],
config_path=assets_root / v['config_file'],
style_vec_path=assets_root / v['style_file'],
device="cpu",
)
for n, seg in enumerate(segments):
if k == seg['speaker']:
text = seg['text']
sr, audio = model.infer(text=text, style=v['style'])
write_wav(f'../data/outputs/sound_{n}.wav', sr, audio)
最後に、分割された音声ファイルを結合して完成。
まとめ
有料ツールや高価なGPUを使わずにトライしましたが、ここまでやるのに結構時間がかかってしまった。主に音声合成で、どんなモデルやライブラリを使うべきか知見がなく、調べて試してを繰り返したことが原因。
翻訳語にも英単語が残っている箇所や人名などの固有名詞部分は期待通りの挙動にならなかった。これは、合成音声する際に英単語や固有名詞の読み方を辞書に登録していないため。
他にも、英語テキストを短いチャンクに分割して翻訳しているので、対話が途切れている感じが残る部分もあります。