sakasegawa
fix
ac8601f
import os
import wave
import numpy as np
import contextlib
from pydub import AudioSegment
from pyannote.core import Segment
from pyannote.audio import Audio
from pyannote.audio.pipelines.speaker_verification import PretrainedSpeakerEmbedding
import torch
from typing import Dict, List, Tuple
def convert_to_wav(input_file: str, output_file: str = "output_file.wav") -> str:
"""
音声ファイルをWAV形式に変換します。
Parameters
----------
input_file: str
変換する音声ファイルのパス
output_file: str
変換後のWAVファイルの出力先パス(デフォルトは"output_file.wav")
Returns
-------
str
変換後のWAVファイルのパス
"""
file_format = os.path.splitext(input_file)[1][1:]
audio = AudioSegment.from_file(input_file, format=file_format)
audio.export(output_file, format="wav")
return output_file
def segment_embedding(
file_name: str,
duration: float,
segment,
embedding_model: PretrainedSpeakerEmbedding
) -> np.ndarray:
"""
音声ファイルから指定されたセグメントの埋め込みを計算します。
Parameters
----------
file_name: str
音声ファイルのパス
duration: float
音声ファイルの継続時間
segment: whisperのtranscribeのsegment
embedding_model: PretrainedSpeakerEmbedding
埋め込みモデル
Returns
-------
np.ndarray
計算された埋め込みベクトル
"""
audio = Audio()
start = segment["start"]
end = min(duration, segment["end"])
clip = Segment(start, end)
waveform, sample_rate = audio.crop(file_name, clip)
return embedding_model(waveform[None])
def reference_audio_embedding(
file_name: str
) -> np.ndarray:
"""
参考音声の埋め込みを出力します。
Parameters
----------
file_name: str
音声ファイルのパス
Returns
-------
np.ndarray
計算された埋め込みベクトル
"""
audio = Audio()
waveform, sample_rate = audio(file_name)
embedding_model = embedding_model = PretrainedSpeakerEmbedding("speechbrain/spkrec-ecapa-voxceleb", device='cpu')
return embedding_model(waveform[None])[0]
def generate_speaker_embeddings(
meeting_file_path: str,
transcript
) -> np.ndarray:
"""
音声ファイルから話者の埋め込みを計算します。
Parameters
----------
meeting_file_path: str
音声ファイルのパス
transcript: Whisper API の transcribe メソッドの出力結果
Returns
-------
np.ndarray
計算された話者の埋め込み群
"""
output_file = convert_to_wav(meeting_file_path)
segments = transcript['segments']
embedding_model = PretrainedSpeakerEmbedding("speechbrain/spkrec-ecapa-voxceleb", device='cpu')
embeddings = np.zeros(shape=(len(segments), 192))
with contextlib.closing(wave.open(output_file, 'r')) as f:
frames = f.getnframes()
rate = f.getframerate()
duration = frames / float(rate)
for i, segment in enumerate(segments):
embeddings[i] = segment_embedding(output_file, duration, segment, embedding_model)
embeddings = np.nan_to_num(embeddings)
return embeddings
import numpy as np
from sklearn.cluster import AgglomerativeClustering
from typing import List, Tuple
def clustering_embeddings(speaker_count: int, embeddings: np.ndarray) -> AgglomerativeClustering:
"""
埋め込みデータをクラスタリングして、クラスタリングオブジェクトを返します。
Parameters
----------
embeddings: np.ndarray
分散表現(埋め込み)のリスト。
Returns
-------
AgglomerativeClustering
クラスタリングオブジェクト。
"""
clustering = AgglomerativeClustering(speaker_count).fit(embeddings)
return clustering
def format_speaker_output_by_segment(clustering: AgglomerativeClustering, transcript: dict) -> str:
"""
クラスタリングの結果をもとに、各発話者ごとにセグメントを整形して出力します
Parameters
----------
clustering: AgglomerativeClustering
クラスタリングオブジェクト。
transcript: dict
Whisper API の transcribe メソッドの出力結果
Returns
-------
str
発話者ごとに整形されたセグメントの文字列
"""
labeled_segments = []
for label, segment in zip(clustering.labels_, transcript["segments"]):
labeled_segments.append((label, segment["start"], segment["text"]))
output = ""
for speaker, _, text in labeled_segments:
output += f"話者{speaker + 1}: 「{text}」\n"
return output
from sklearn.cluster import KMeans
from sklearn.metrics.pairwise import pairwise_distances
def clustering_embeddings2(speaker_count: int, embeddings: np.ndarray) -> KMeans:
"""
埋め込みデータをクラスタリングして、クラスタリングオブジェクトを返します。
Parameters
----------
embeddings: np.ndarray
分散表現(埋め込み)のリスト。
Returns
-------
KMeans
クラスタリングオブジェクト。
"""
# コサイン類似度行列を計算
cosine_distances = pairwise_distances(embeddings, metric='cosine')
clustering = KMeans(n_clusters=speaker_count).fit(cosine_distances)
return clustering
from scipy.spatial.distance import cosine
def closest_reference_speaker(embedding: np.ndarray, references: List[Tuple[str, np.ndarray]]) -> str:
"""
与えられた埋め込みに最も近い参照話者を返します。
Parameters
----------
embedding: np.ndarray
話者の埋め込み
references: List[Tuple[str, np.ndarray]]
参照話者の名前と埋め込みのリスト
Returns
-------
str
最も近い参照話者の名前
"""
min_distance = float('inf')
closest_speaker = None
for name, reference_embedding in references:
distance = cosine(embedding, reference_embedding)
if distance < min_distance:
min_distance = distance
closest_speaker = name
return closest_speaker
def format_speaker_output_by_segment2(embeddings: np.ndarray, transcript: dict, reference_embeddings: List[Tuple[str, np.ndarray]]) -> str:
"""
各発話者の埋め込みに基づいて、セグメントを整形して出力します。
Parameters
----------
embeddings: np.ndarray
話者の埋め込みのリスト
transcript: dict
Whisper API の transcribe メソッドの出力結果
reference_embeddings: List[Tuple[str, np.ndarray]]
参照話者の名前と埋め込みのリスト
Returns
-------
str
発話者ごとに整形されたセグメントの文字列。
"""
labeled_segments = []
for embedding, segment in zip(embeddings, transcript["segments"]):
speaker_name = closest_reference_speaker(embedding, reference_embeddings)
labeled_segments.append((speaker_name, segment["start"], segment["text"]))
output = ""
for speaker, _, text in labeled_segments:
output += f"{speaker}: 「{text}」\n"
return output
import gradio as gr
import openai
def create_transcription_with_speaker(openai_key, main_audio, reference_audio_1, reference1_name,
reference_audio_2, reference2_name, speaker_count = 2):
openai.api_key = openai_key
# 文字起こし
transcript = openai.Audio.transcribe("whisper-1", open(main_audio, "rb"), response_format="verbose_json")
# 各発話をembeddingsに変換
embeddings = generate_speaker_embeddings(main_audio, transcript)
# 各発話のembeddingsをクラスタリング
clustering = clustering_embeddings(speaker_count, embeddings)
# クラスタリングで作られた仮のラベルで各セグメントに名前付け
output_by_segment1 = format_speaker_output_by_segment(clustering, transcript)
reference1 = reference_audio_embedding(reference_audio_1)
reference2 = reference_audio_embedding(reference_audio_2)
reference_embeddings = [(reference1_name, reference1), (reference2_name, reference2)]
output_by_segment2 = format_speaker_output_by_segment2(embeddings, transcript, reference_embeddings)
return output_by_segment1, output_by_segment2
inputs = [
gr.Textbox(lines=1, label="openai_key", type="password"),
gr.Audio(type="filepath", label="メイン音声ファイル"),
gr.Audio(type="filepath", label="話者 (1) 参考音声ファイル"),
gr.Textbox(lines=1, label="話者 (1) の名前"),
gr.Audio(type="filepath", label="話者 (2) 参考音声ファイル"),
gr.Textbox(lines=1, label="話者 (2) の名前")
]
outputs = [
gr.Textbox(label="話者クラスタリング文字起こし"),
gr.Textbox(label="話者アサイン文字起こし"),
]
app = gr.Interface(
fn=create_transcription_with_speaker,
inputs=inputs,
outputs=outputs,
title="話者アサイン機能付き書き起こしアプリ",
description="音声ファイルをアップロードすると、各話者の名前がアサインされた文字起こしが作成されます。参考: https://huggingface.co/spaces/vumichien/Whisper_speaker_diarization"
)
app.launch(debug=True)