sakasegawa commited on
Commit
a169690
1 Parent(s): c059ce0

Initial Commit

Browse files
Files changed (2) hide show
  1. app.py +286 -0
  2. requirements.txt +2 -0
app.py ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import wave
3
+ import numpy as np
4
+ import contextlib
5
+ from pydub import AudioSegment
6
+ from pyannote.core import Segment
7
+ from pyannote.audio import Audio
8
+ from pyannote.audio.pipelines.speaker_verification import PretrainedSpeakerEmbedding
9
+ import torch
10
+ from typing import Dict, List, Tuple
11
+
12
+
13
+ def convert_to_wav(input_file: str, output_file: str = "output_file.wav") -> str:
14
+ """
15
+ 音声ファイルをWAV形式に変換します。
16
+
17
+ Parameters
18
+ ----------
19
+ input_file: str
20
+ 変換する音声ファイルのパス
21
+ output_file: str
22
+ 変換後のWAVファイルの出力先パス(デフォルトは"output_file.wav")
23
+
24
+ Returns
25
+ -------
26
+ str
27
+ 変換後のWAVファイルのパス
28
+ """
29
+ file_format = os.path.splitext(input_file)[1][1:]
30
+ audio = AudioSegment.from_file(input_file, format=file_format)
31
+ audio.export(output_file, format="wav")
32
+ return output_file
33
+
34
+ def segment_embedding(
35
+ file_name: str,
36
+ duration: float,
37
+ segment,
38
+ embedding_model: PretrainedSpeakerEmbedding
39
+ ) -> np.ndarray:
40
+ """
41
+ 音声ファイルから指定されたセグメントの埋め込みを計算します。
42
+
43
+ Parameters
44
+ ----------
45
+ file_name: str
46
+ 音声ファイルのパス
47
+ duration: float
48
+ 音声ファイルの継続時間
49
+ segment: whisperのtranscribeのsegment
50
+ embedding_model: PretrainedSpeakerEmbedding
51
+ 埋め込みモデル
52
+
53
+ Returns
54
+ -------
55
+ np.ndarray
56
+ 計算された埋め込みベクトル
57
+ """
58
+ audio = Audio()
59
+ start = segment["start"]
60
+ end = min(duration, segment["end"])
61
+ clip = Segment(start, end)
62
+ waveform, sample_rate = audio.crop(file_name, clip)
63
+ return embedding_model(waveform[None])
64
+
65
+ def reference_audio_embedding(
66
+ file_name: str
67
+ ) -> np.ndarray:
68
+ """
69
+ 参考音声の埋め込みを出力します。
70
+
71
+ Parameters
72
+ ----------
73
+ file_name: str
74
+ 音声ファイルのパス
75
+
76
+ Returns
77
+ -------
78
+ np.ndarray
79
+ 計算された埋め込みベクトル
80
+ """
81
+ audio = Audio()
82
+ waveform, sample_rate = audio(file_name)
83
+ embedding_model = embedding_model = PretrainedSpeakerEmbedding("speechbrain/spkrec-ecapa-voxceleb", device='cpu')
84
+ return embedding_model(waveform[None])[0]
85
+
86
+ def generate_speaker_embeddings(
87
+ meeting_file_path: str,
88
+ transcript
89
+ ) -> np.ndarray:
90
+ """
91
+ 音声ファイルから話者の埋め込みを計算します。
92
+
93
+ Parameters
94
+ ----------
95
+ meeting_file_path: str
96
+ 音声ファイルのパス
97
+ transcript: Whisper API の transcribe メソッドの出力結果
98
+
99
+ Returns
100
+ -------
101
+ np.ndarray
102
+ 計算された話者の埋め込み群
103
+ """
104
+ output_file = convert_to_wav(meeting_file_path)
105
+
106
+ segments = transcript['segments']
107
+ embedding_model = PretrainedSpeakerEmbedding("speechbrain/spkrec-ecapa-voxceleb", device='cpu')
108
+ embeddings = np.zeros(shape=(len(segments), 192))
109
+
110
+ with contextlib.closing(wave.open(output_file, 'r')) as f:
111
+ frames = f.getnframes()
112
+ rate = f.getframerate()
113
+ duration = frames / float(rate)
114
+
115
+ for i, segment in enumerate(segments):
116
+ embeddings[i] = segment_embedding(output_file, duration, segment, embedding_model)
117
+
118
+ embeddings = np.nan_to_num(embeddings)
119
+ return embeddings
120
+
121
+ import numpy as np
122
+ from sklearn.cluster import AgglomerativeClustering
123
+ from typing import List, Tuple
124
+
125
+ def clustering_embeddings(speaker_count: int, embeddings: np.ndarray) -> AgglomerativeClustering:
126
+ """
127
+ 埋め込みデータをクラスタリングして、クラスタリングオブジェクトを返します。
128
+
129
+ Parameters
130
+ ----------
131
+ embeddings: np.ndarray
132
+ 分散表現(埋め込み)のリスト。
133
+
134
+ Returns
135
+ -------
136
+ AgglomerativeClustering
137
+ クラスタリングオブジェクト。
138
+ """
139
+ clustering = AgglomerativeClustering(speaker_count).fit(embeddings)
140
+ return clustering
141
+
142
+ def format_speaker_output_by_segment(clustering: AgglomerativeClustering, transcript: dict) -> str:
143
+ """
144
+ クラスタリングの結果をもとに、各発話者ごとにセグメントを整形して出力します
145
+
146
+ Parameters
147
+ ----------
148
+ clustering: AgglomerativeClustering
149
+ クラスタリングオブジェクト。
150
+ transcript: dict
151
+ Whisper API の transcribe メソッドの出力結果
152
+
153
+ Returns
154
+ -------
155
+ str
156
+ 発話者ごとに整形されたセグメントの文字列
157
+ """
158
+ labeled_segments = []
159
+ for label, segment in zip(clustering.labels_, transcript["segments"]):
160
+ labeled_segments.append((label, segment["start"], segment["text"]))
161
+
162
+ output = ""
163
+ for speaker, _, text in labeled_segments:
164
+ output += f"話者{speaker + 1}: 「{text}」\n"
165
+ return output
166
+
167
+ from sklearn.cluster import KMeans
168
+ from sklearn.metrics.pairwise import pairwise_distances
169
+ def clustering_embeddings2(speaker_count: int, embeddings: np.ndarray) -> KMeans:
170
+ """
171
+ 埋め込みデータをクラスタリングして、クラスタリングオブジェクトを返します。
172
+
173
+ Parameters
174
+ ----------
175
+ embeddings: np.ndarray
176
+ 分散表現(埋め込み)のリスト。
177
+
178
+ Returns
179
+ -------
180
+ KMeans
181
+ クラスタリングオブジェクト。
182
+ """
183
+ # コサイン類似度行列を計算
184
+ cosine_distances = pairwise_distances(embeddings, metric='cosine')
185
+ clustering = KMeans(n_clusters=speaker_count).fit(cosine_distances)
186
+ return clustering
187
+
188
+ from scipy.spatial.distance import cosine
189
+
190
+ def closest_reference_speaker(embedding: np.ndarray, references: List[Tuple[str, np.ndarray]]) -> str:
191
+ """
192
+ 与えられた埋め込みに最も近い参照話者を返します。
193
+
194
+ Parameters
195
+ ----------
196
+ embedding: np.ndarray
197
+ 話者の埋め込み
198
+ references: List[Tuple[str, np.ndarray]]
199
+ 参照話者の名前と埋め込みのリスト
200
+
201
+ Returns
202
+ -------
203
+ str
204
+ 最も近い参照話者の名前
205
+ """
206
+ min_distance = float('inf')
207
+ closest_speaker = None
208
+ for name, reference_embedding in references:
209
+ distance = cosine(embedding, reference_embedding)
210
+ if distance < min_distance:
211
+ min_distance = distance
212
+ closest_speaker = name
213
+
214
+ return closest_speaker
215
+
216
+ def format_speaker_output_by_segment2(embeddings: np.ndarray, transcript: dict, reference_embeddings: List[Tuple[str, np.ndarray]]) -> str:
217
+ """
218
+ 各発話者の埋め込みに基づいて、セグメントを整形して出力します。
219
+
220
+ Parameters
221
+ ----------
222
+ embeddings: np.ndarray
223
+ 話者の埋め込みのリスト
224
+ transcript: dict
225
+ Whisper API の transcribe メソッドの出力結果
226
+ reference_embeddings: List[Tuple[str, np.ndarray]]
227
+ 参照話者の名前と埋め込みのリスト
228
+
229
+ Returns
230
+ -------
231
+ str
232
+ 発話者ごとに整形されたセグメントの文字列。
233
+ """
234
+ labeled_segments = []
235
+ for embedding, segment in zip(embeddings, transcript["segments"]):
236
+ speaker_name = closest_reference_speaker(embedding, reference_embeddings)
237
+ labeled_segments.append((speaker_name, segment["start"], segment["text"]))
238
+
239
+ output = ""
240
+ for speaker, _, text in labeled_segments:
241
+ output += f"{speaker}: 「{text}」\n"
242
+ return output
243
+
244
+ import gradio as gr
245
+ import openai
246
+
247
+ def create_transcription_with_speaker(openai_key, main_audio, reference_audio_1, reference1_name,
248
+ reference_audio_2, reference2_name, speaker_count = 2):
249
+ openai.api_key = openai_key
250
+ # 文字起こし
251
+ transcript = openai.Audio.transcribe("whisper-1", open(main_audio, "rb"), response_format="verbose_json")
252
+ # 各発話をembeddingsに変換
253
+ embeddings = generate_speaker_embeddings(main_audio, transcript)
254
+ # 各発話のembeddingsをクラスタリング
255
+ clustering = clustering_embeddings(speaker_count, embeddings)
256
+ # クラスタリングで作られた仮のラベルで各セグメントに名前付け
257
+ output_by_segment1 = format_speaker_output_by_segment(clustering, transcript)
258
+ reference1 = reference_audio_embedding(reference_audio_1)
259
+ reference2 = reference_audio_embedding(reference_audio_2)
260
+ reference_embeddings = [(reference1_name, reference1), (reference2_name, reference2)]
261
+ output_by_segment2 = format_speaker_output_by_segment2(embeddings, transcript, reference_embeddings)
262
+ return output_by_segment1, output_by_segment2
263
+
264
+ inputs = [
265
+ gr.Textbox(lines=1, label="openai_key"),
266
+ gr.Audio(type="filepath", label="メイン音声ファイル"),
267
+ gr.Audio(type="filepath", label="話者 (1) 参考音声ファイル"),
268
+ gr.Textbox(lines=1, label="話者 (1) の名前"),
269
+ gr.Audio(type="filepath", label="話者 (2) 参考音声ファイル"),
270
+ gr.Textbox(lines=1, label="話者 (2) の名前")
271
+ ]
272
+
273
+ outputs = [
274
+ gr.Textbox(label="話者クラスタリング文字起こし"),
275
+ gr.Textbox(label="話者アサイン文字起こし"),
276
+ ]
277
+
278
+ app = gr.Interface(
279
+ fn=create_transcription_with_speaker,
280
+ inputs=inputs,
281
+ outputs=outputs,
282
+ title="話者アサイン機能付き書き起こしアプリ",
283
+ description="音声ファイルをアップロードすると、各話者の名前がアサインされた文字起こしが作成されます。"
284
+ )
285
+
286
+ app.launch(debug=True)
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ openai==0.27.2
2
+ pyannote.audio