fosters commited on
Commit
3499f02
·
verified ·
1 Parent(s): 42abd96

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +52 -17
app.py CHANGED
@@ -12,7 +12,6 @@ from transformers import Wav2Vec2FeatureExtractor, WavLMForXVector
12
 
13
  MODEL_ID = "microsoft/wavlm-base-plus-sv"
14
  TARGET_SR = 16000
15
- MAX_AUDIO_SEC = 5 # speaker identity stabilizes in 5 sec; shorter = 4x faster on CPU
16
 
17
  _feature_extractor = None
18
  _model = None
@@ -27,14 +26,14 @@ def _load_model():
27
  return _feature_extractor, _model
28
 
29
 
30
- def _embed(audio_array: np.ndarray, sr: int) -> np.ndarray:
31
  fe, mdl = _load_model()
32
  waveform = torch.tensor(audio_array, dtype=torch.float32)
33
  if waveform.ndim == 2:
34
  waveform = waveform.mean(0)
35
  if sr != TARGET_SR:
36
  waveform = torchaudio.functional.resample(waveform, sr, TARGET_SR)
37
- waveform = waveform[: MAX_AUDIO_SEC * TARGET_SR]
38
  inputs = fe(waveform.numpy(), sampling_rate=TARGET_SR, return_tensors="pt")
39
  with torch.no_grad():
40
  out = mdl(**inputs)
@@ -44,6 +43,7 @@ def _embed(audio_array: np.ndarray, sr: int) -> np.ndarray:
44
  def identify_speakers(
45
  repo_ids_text: str,
46
  samples_per_book: int,
 
47
  threshold: float,
48
  hf_token: str,
49
  progress=gr.Progress(),
@@ -73,7 +73,7 @@ def identify_speakers(
73
  raw = row["audio"]
74
  audio_bytes = raw.get("bytes") or open(raw["path"], "rb").read()
75
  audio_array, sr = sf.read(io.BytesIO(audio_bytes))
76
- embs.append(_embed(audio_array, sr))
77
  if embs:
78
  embeddings[repo] = np.mean(embs, axis=0)
79
  else:
@@ -126,34 +126,69 @@ def identify_speakers(
126
  return df, summary, "\n".join(errors) if errors else "None"
127
 
128
 
129
- with gr.Blocks(title="Speaker Identifier") as demo:
130
- gr.Markdown(
131
- """
132
  # 🎙️ Speaker Identifier
133
- Extract speaker embeddings from HF audio datasets and cluster into unique speakers.
134
- Uses **WavLM-Base+** (`microsoft/wavlm-base-plus-sv`) language-agnostic, works for any language.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  """
136
- )
 
 
 
137
  with gr.Row():
138
  with gr.Column(scale=2):
139
  repo_input = gr.Textbox(
140
- label="Dataset repo IDs (one per line, owner/name)",
141
  placeholder="fosters/some_audiobook_output\nfosters/another_audiobook_output",
142
- lines=14,
143
  )
144
  with gr.Column(scale=1):
145
  samples = gr.Slider(1, 10, value=3, step=1, label="Samples per book")
 
 
 
 
 
146
  threshold = gr.Slider(
147
  0.60, 0.98, value=0.82, step=0.01,
148
- label="Same-speaker threshold (cosine similarity)",
149
- info="Higher = stricter matching, more clusters",
150
  )
151
  hf_token = gr.Textbox(
152
- label="HF Token (only for private repos)",
153
  type="password",
154
  placeholder="hf_…",
155
  )
156
- run_btn = gr.Button("Identify Speakers", variant="primary")
157
 
158
  summary_out = gr.Textbox(label="Summary", interactive=False)
159
  table_out = gr.Dataframe(
@@ -165,7 +200,7 @@ Uses **WavLM-Base+** (`microsoft/wavlm-base-plus-sv`) — language-agnostic, wor
165
 
166
  run_btn.click(
167
  identify_speakers,
168
- inputs=[repo_input, samples, threshold, hf_token],
169
  outputs=[table_out, summary_out, errors_out],
170
  )
171
 
 
12
 
13
  MODEL_ID = "microsoft/wavlm-base-plus-sv"
14
  TARGET_SR = 16000
 
15
 
16
  _feature_extractor = None
17
  _model = None
 
26
  return _feature_extractor, _model
27
 
28
 
29
+ def _embed(audio_array: np.ndarray, sr: int, max_sec: int) -> np.ndarray:
30
  fe, mdl = _load_model()
31
  waveform = torch.tensor(audio_array, dtype=torch.float32)
32
  if waveform.ndim == 2:
33
  waveform = waveform.mean(0)
34
  if sr != TARGET_SR:
35
  waveform = torchaudio.functional.resample(waveform, sr, TARGET_SR)
36
+ waveform = waveform[: max_sec * TARGET_SR]
37
  inputs = fe(waveform.numpy(), sampling_rate=TARGET_SR, return_tensors="pt")
38
  with torch.no_grad():
39
  out = mdl(**inputs)
 
43
  def identify_speakers(
44
  repo_ids_text: str,
45
  samples_per_book: int,
46
+ audio_sec: int,
47
  threshold: float,
48
  hf_token: str,
49
  progress=gr.Progress(),
 
73
  raw = row["audio"]
74
  audio_bytes = raw.get("bytes") or open(raw["path"], "rb").read()
75
  audio_array, sr = sf.read(io.BytesIO(audio_bytes))
76
+ embs.append(_embed(audio_array, sr, int(audio_sec)))
77
  if embs:
78
  embeddings[repo] = np.mean(embs, axis=0)
79
  else:
 
126
  return df, summary, "\n".join(errors) if errors else "None"
127
 
128
 
129
+ DESCRIPTION = """
 
 
130
  # 🎙️ Speaker Identifier
131
+
132
+ Finds unique speakers across multiple HF audio datasets. Each dataset is assumed to have
133
+ **one speaker** (e.g. an audiobook). The app extracts voice embeddings and clusters datasets
134
+ by voice similarity.
135
+
136
+ **Model:** [microsoft/wavlm-base-plus-sv](https://huggingface.co/microsoft/wavlm-base-plus-sv) —
137
+ language-agnostic speaker embeddings, works for any language.
138
+
139
+ ---
140
+
141
+ ## How to use
142
+
143
+ 1. **Paste dataset repo IDs** (one per line, `owner/name` format) into the left box.
144
+ 2. **Adjust parameters** if needed (defaults work well for audiobooks):
145
+ - *Samples per book* — how many audio chunks to average per dataset. More = more robust, slower.
146
+ - *Audio length (sec)* — how many seconds of each chunk to use. 5 sec is enough for a clear voice; increase if results look wrong.
147
+ - *Same-speaker threshold* — cosine similarity cutoff. Raise it if too many books are merged into one speaker; lower it if one person gets split into multiple IDs.
148
+ - *HF Token* — only needed for **private** repos.
149
+ 3. Click **Identify Speakers**.
150
+
151
+ ## Output columns
152
+
153
+ | Column | Meaning |
154
+ |---|---|
155
+ | `dataset` | Repo name (short) |
156
+ | `speaker_id` | Cluster label — same ID means same voice |
157
+ | `books_with_speaker` | How many books share this speaker |
158
+ | `intra_sim` | Avg cosine similarity between books with the same speaker (1.0 = only one book; lower = cluster is less tight) |
159
+ | `closest_match` | Most similar other book and its similarity score |
160
+
161
+ **Tip:** Sort by `speaker_id` to see all books by the same narrator grouped together.
162
  """
163
+
164
+ with gr.Blocks(title="Speaker Identifier") as demo:
165
+ gr.Markdown(DESCRIPTION)
166
+ gr.Markdown("---")
167
  with gr.Row():
168
  with gr.Column(scale=2):
169
  repo_input = gr.Textbox(
170
+ label="Dataset repo IDs (one per line)",
171
  placeholder="fosters/some_audiobook_output\nfosters/another_audiobook_output",
172
+ lines=16,
173
  )
174
  with gr.Column(scale=1):
175
  samples = gr.Slider(1, 10, value=3, step=1, label="Samples per book")
176
+ audio_sec = gr.Slider(
177
+ 2, 30, value=5, step=1,
178
+ label="Audio length per sample (sec)",
179
+ info="5 sec is usually enough; longer = more accurate but slower",
180
+ )
181
  threshold = gr.Slider(
182
  0.60, 0.98, value=0.82, step=0.01,
183
+ label="Same-speaker threshold",
184
+ info="Higher = stricter matching more clusters",
185
  )
186
  hf_token = gr.Textbox(
187
+ label="HF Token (private repos only)",
188
  type="password",
189
  placeholder="hf_…",
190
  )
191
+ run_btn = gr.Button("Identify Speakers", variant="primary", size="lg")
192
 
193
  summary_out = gr.Textbox(label="Summary", interactive=False)
194
  table_out = gr.Dataframe(
 
200
 
201
  run_btn.click(
202
  identify_speakers,
203
+ inputs=[repo_input, samples, audio_sec, threshold, hf_token],
204
  outputs=[table_out, summary_out, errors_out],
205
  )
206