ozspeech commited on
Commit
268786d
·
1 Parent(s): 43396d9
Files changed (4) hide show
  1. README.md +38 -13
  2. app.py +615 -0
  3. display_text.py +156 -0
  4. requirements.txt +2 -0
README.md CHANGED
@@ -1,13 +1,38 @@
1
- ---
2
- title: DiFlow TTS
3
- emoji: 👀
4
- colorFrom: gray
5
- colorTo: indigo
6
- sdk: gradio
7
- sdk_version: 5.38.0
8
- app_file: app.py
9
- pinned: false
10
- short_description: MOS Evaluation
11
- ---
12
-
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MOS Evaluation Application
2
+
3
+ ## Introduction
4
+ This application is designed for Mean Opinion Score (MOS) evaluation of audio samples.
5
+
6
+ ## Installation
7
+ 1. Set up a Conda environment.
8
+ 2. Install the required dependencies by running:
9
+
10
+ ```shell
11
+ pip install -r requirements.txt
12
+ ```
13
+
14
+ ## Quick Start
15
+
16
+ To launch the application, execute:
17
+ ```shell
18
+ python app.py
19
+ ```
20
+
21
+ Evaluation results will be saved into `results` directory.
22
+
23
+ ## Data Preparation
24
+ Before running the application, prepare a folder containing `N` CSV files, where `N` is the number of participants expected to perform evaluations.
25
+
26
+ Each CSV file should have four columns with `n` rows (`n` can vary between files):
27
+ - `filepath`: Path to the audio file being evaluated.
28
+ - `gt`: Ground-truth audio corresponding to `filepath`.
29
+ - `model`: The model that generated `filepath`.
30
+ - `transcript`: Ground-truth transcript of `filepath`.
31
+
32
+ Refer to the sample in the [data](./samples/data) directory.
33
+
34
+ ## How Does This Tool Work?
35
+
36
+ The `MOSApp` class in `app.py` loads and stores each CSV file as an attribute. When users enter their IDs, the application automatically assigns a unique CSV file to each user, ensuring that no two users evaluate the same file.
37
+
38
+ Each user's progress is saved in the `progress` directory. If a user returns to finish their evaluation and enters the same ID as before, their previous progress will be automatically restored.
app.py ADDED
@@ -0,0 +1,615 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import os
3
+ import json
4
+ import pandas as pd
5
+ import gradio as gr
6
+ from datetime import datetime
7
+ from display_text import DESCRIPTIONS
8
+
9
+
10
+ class MOSApp:
11
+
12
+ MOS_SCORES = {
13
+ "1 - Bad": 1,
14
+ "1.5": 1.5,
15
+ "2 - Poor": 2,
16
+ "2.5": 2.5,
17
+ "3 - Fair": 3,
18
+ "3.5": 3.5,
19
+ "4 - Good": 4,
20
+ "4.5": 4.5,
21
+ "5 - Excellent": 5
22
+ }
23
+
24
+ def __init__(self, dirpath: str, outdir: str, progress_dir: str):
25
+ csv_files = os.listdir(dirpath)
26
+ self.dfs = [pd.read_csv(os.path.join(dirpath, f)) for f in csv_files]
27
+ self.current_files = None
28
+ self.current_transcripts = None
29
+ self.current_models = None
30
+ self.current_gt = None
31
+ self.id_to_take = 0
32
+ self.outdir = outdir
33
+ self.rev_mos = {v: k for k, v in self.MOS_SCORES.items()}
34
+ os.makedirs(outdir, exist_ok=True)
35
+ self.progress_dir = progress_dir
36
+ os.makedirs(progress_dir, exist_ok=True)
37
+
38
+ def save_state(self, state):
39
+ """Save the current state to a JSON file using tester_id as filename."""
40
+ if state.get("tester_id"):
41
+ progress_path = os.path.join(self.progress_dir, f"{state['tester_id']}.json")
42
+ with open(progress_path, "w") as f:
43
+ json.dump(state, f)
44
+ return
45
+
46
+ def load_state(self, tester_id):
47
+ """Load the state for a given tester_id if it exists."""
48
+ progress_path = os.path.join(self.progress_dir, f"{tester_id}.json")
49
+ if os.path.exists(progress_path):
50
+ with open(progress_path, "r") as f:
51
+ return json.load(f)
52
+ return None
53
+
54
+ def get_current_info(self):
55
+ if self.id_to_take >= len(self.dfs):
56
+ self.id_to_take = 0
57
+ self.current_files = self.dfs[self.id_to_take]["filepath"].tolist()
58
+ self.current_transcripts = self.dfs[self.id_to_take]["transcript"].tolist()
59
+ self.current_models = self.dfs[self.id_to_take]["model"].tolist()
60
+ self.current_gt = self.dfs[self.id_to_take]["gt"].tolist()
61
+ self.id_to_take += 1
62
+ return (
63
+ self.current_files,
64
+ self.current_transcripts,
65
+ self.current_models,
66
+ self.current_gt,
67
+ )
68
+
69
+ def initialize_state(self):
70
+ return {
71
+ "index": 0,
72
+ "selected_naturalness_MOS": [],
73
+ "selected_intelligibility_MOS": [],
74
+ "selected_similarity_MOS": [],
75
+ "tester_id": "",
76
+ "current_files": None,
77
+ "current_transcripts": None,
78
+ "current_models": None,
79
+ "current_gt": None,
80
+ }
81
+
82
+ def submit_options(self, naturalness, intelligibility, similarity, state):
83
+
84
+ # Warn if any score is not selected
85
+ if naturalness is None:
86
+ gr.Warning("Please rate NATURALNESS before submitting.", duration=5)
87
+ return (
88
+ state["current_files"][state["index"]],
89
+ naturalness,
90
+ intelligibility,
91
+ similarity,
92
+ state,
93
+ state["current_transcripts"][state["index"]],
94
+ state["current_gt"][state["index"]],
95
+ gr.update(),
96
+ gr.update(),
97
+ gr.update(),
98
+ )
99
+ if intelligibility is None:
100
+ gr.Warning("Please rate INTELLIGIBILITY before submitting.", duration=5)
101
+ return (
102
+ state["current_files"][state["index"]],
103
+ naturalness,
104
+ intelligibility,
105
+ similarity,
106
+ state,
107
+ state["current_transcripts"][state["index"]],
108
+ state["current_gt"][state["index"]],
109
+ gr.update(),
110
+ gr.update(),
111
+ gr.update(),
112
+ )
113
+ if similarity is None:
114
+ gr.Warning("Please rate SIMILARITY before submitting.", duration=5)
115
+ return (
116
+ state["current_files"][state["index"]],
117
+ naturalness,
118
+ intelligibility,
119
+ similarity,
120
+ state,
121
+ state["current_transcripts"][state["index"]],
122
+ state["current_gt"][state["index"]],
123
+ gr.update(),
124
+ gr.update(),
125
+ gr.update(),
126
+ )
127
+
128
+ current_files = state["current_files"]
129
+ if not current_files:
130
+ return (
131
+ None,
132
+ None,
133
+ None,
134
+ None,
135
+ state,
136
+ "",
137
+ None,
138
+ gr.update(),
139
+ gr.update(),
140
+ gr.update(),
141
+ )
142
+
143
+ submitted_count = len(state["selected_naturalness_MOS"])
144
+
145
+ # If the current index is less than submitted_count, we are editing a past evaluation.
146
+ if state["index"] < submitted_count:
147
+ state["selected_naturalness_MOS"][state["index"]] = self.MOS_SCORES[naturalness]
148
+ state["selected_intelligibility_MOS"][state["index"]] = self.MOS_SCORES[intelligibility]
149
+ state["selected_similarity_MOS"][state["index"]] = self.MOS_SCORES[similarity]
150
+ state["index"] += 1
151
+ audio = current_files[state["index"]]
152
+ transcript = state["current_transcripts"][state["index"]]
153
+ gt = state["current_gt"][state["index"]]
154
+ self.save_state(state)
155
+ elif state["index"] == submitted_count:
156
+ # New evaluation: append the scores.
157
+ state["selected_naturalness_MOS"].append(self.MOS_SCORES[naturalness])
158
+ state["selected_intelligibility_MOS"].append(self.MOS_SCORES[intelligibility])
159
+ state["selected_similarity_MOS"].append(self.MOS_SCORES[similarity])
160
+ state["index"] += 1 # Move to the next evaluation.
161
+ if state["index"] < len(current_files):
162
+ audio = current_files[state["index"]]
163
+ transcript = state["current_transcripts"][state["index"]]
164
+ gt = state["current_gt"][state["index"]]
165
+ else:
166
+ audio, transcript, gt = None, "", None
167
+ self.save_state(state)
168
+ else:
169
+ audio, transcript, gt = None, "", None
170
+
171
+ # If the user has finished all evaluations, save CSV.
172
+ if state["index"] >= len(current_files):
173
+ results_df = pd.DataFrame({
174
+ "filepath": state["current_files"],
175
+ "model": state["current_models"],
176
+ "Natural-MOS": state["selected_naturalness_MOS"],
177
+ "Intelligibility-MOS": state["selected_intelligibility_MOS"],
178
+ "Similarity-MOS": state["selected_similarity_MOS"],
179
+ })
180
+ csv_path = os.path.join(self.outdir, f"{state['tester_id']}.csv")
181
+ results_df.to_csv(csv_path, index=False)
182
+ gr.Success("Thank you for your feedback! Evaluation finished.", duration=5)
183
+ # Disable navigation buttons when finished.
184
+ return (
185
+ None,
186
+ None,
187
+ None,
188
+ None,
189
+ state,
190
+ "",
191
+ None,
192
+ gr.update(value=state["index"]),
193
+ gr.update(interactive=False),
194
+ gr.update(interactive=False),
195
+ )
196
+ else:
197
+ # Update navigation buttons
198
+ back_update = gr.update(interactive=True) if state["index"] > 0 \
199
+ else gr.update(interactive=False)
200
+ next_update = gr.update(interactive=True) if state["index"] < submitted_count \
201
+ else gr.update(interactive=False)
202
+ return (
203
+ audio,
204
+ None,
205
+ None,
206
+ None,
207
+ state,
208
+ transcript,
209
+ gt,
210
+ gr.update(value=state["index"] + 1),
211
+ back_update,
212
+ next_update,
213
+ )
214
+
215
+ def set_tester_id(self, id, state):
216
+
217
+ # Try to load an existing state
218
+ loaded_state = self.load_state(id)
219
+
220
+ if loaded_state is not None:
221
+ # Use the loaded state and provide the next audio sample based on saved index.
222
+ state = loaded_state
223
+ id_display_text = f"## Welcome back! Your ID: {state['tester_id']}"
224
+ else:
225
+ # No saved state; initialize a new one.
226
+ (
227
+ state["current_files"],
228
+ state["current_transcripts"],
229
+ state["current_models"],
230
+ state["current_gt"],
231
+ ) = self.get_current_info()
232
+ state["tester_id"] = id
233
+ state["index"] = 0
234
+ # Save the new state
235
+ self.save_state(state)
236
+ id_display_text = f"## Your ID: {state['tester_id']}"
237
+
238
+ return (
239
+ id_display_text,
240
+ state,
241
+ state["current_files"][state["index"]],
242
+ state["current_transcripts"][state["index"]],
243
+ state["current_gt"][state["index"]],
244
+ gr.update(visible=False, interactive=False),
245
+ gr.update(visible=False, interactive=False),
246
+ gr.update(interactive=False),
247
+ gr.update(interactive=True),
248
+ gr.update(interactive=True),
249
+ gr.update(interactive=True),
250
+ gr.update(value=state["index"] + 1),
251
+ )
252
+
253
+ def go_back(self, state):
254
+ submitted_count = len(state["selected_naturalness_MOS"])
255
+ if state["index"] > 0:
256
+ state["index"] -= 1
257
+
258
+ # Load the previously submitted scores
259
+ if state["index"] < submitted_count:
260
+ naturalness = self.rev_mos[state["selected_naturalness_MOS"][state["index"]]]
261
+ intelligibility = self.rev_mos[state["selected_intelligibility_MOS"][state["index"]]]
262
+ similarity = self.rev_mos[state["selected_similarity_MOS"][state["index"]]]
263
+ else:
264
+ naturalness, intelligibility, similarity = None, None, None
265
+
266
+ back_update = gr.update(interactive=True) if state["index"] > 0 \
267
+ else gr.update(interactive=False)
268
+ next_update = gr.update(interactive=True) if state["index"] < submitted_count \
269
+ else gr.update(interactive=False)
270
+
271
+ return (
272
+ state["current_files"][state["index"]],
273
+ state["current_transcripts"][state["index"]],
274
+ state["current_gt"][state["index"]],
275
+ naturalness,
276
+ intelligibility,
277
+ similarity,
278
+ state,
279
+ gr.update(value=state["index"] + 1),
280
+ back_update,
281
+ next_update,
282
+ )
283
+
284
+ def go_next(self, state):
285
+ submitted_count = len(state["selected_naturalness_MOS"])
286
+ if state["index"] < submitted_count:
287
+ state["index"] += 1
288
+
289
+ # Load the next audio sample
290
+ if state["index"] < submitted_count:
291
+ naturalness = self.rev_mos.get(state["selected_naturalness_MOS"][state["index"]], None)
292
+ intelligibility = self.rev_mos.get(state["selected_intelligibility_MOS"][state["index"]], None)
293
+ similarity = self.rev_mos.get(state["selected_similarity_MOS"][state["index"]], None)
294
+ else:
295
+ naturalness, intelligibility, similarity = None, None, None
296
+
297
+ back_update = gr.update(interactive=True) if state["index"] > 0 \
298
+ else gr.update(interactive=False)
299
+ next_update = gr.update(interactive=True) if state["index"] < submitted_count \
300
+ else gr.update(interactive=False)
301
+
302
+ return (
303
+ state["current_files"][state["index"]],
304
+ state["current_transcripts"][state["index"]],
305
+ state["current_gt"][state["index"]],
306
+ naturalness,
307
+ intelligibility,
308
+ similarity,
309
+ state,
310
+ gr.update(value=state["index"] + 1),
311
+ back_update,
312
+ next_update,
313
+ )
314
+
315
+ def toggle_language(self, language_toggle):
316
+ texts = DESCRIPTIONS["English"] if language_toggle else DESCRIPTIONS["Vietnamese"]
317
+ return (
318
+ gr.update(label=texts["language_toggle"]),
319
+ gr.update(value=texts["sidebar"]),
320
+ gr.update(value=texts["naturalness_guidelines"]),
321
+ gr.update(value=texts["intelligibility_guidelines"]),
322
+ gr.update(value=texts["similarity_guidelines"]),
323
+ gr.update(value=texts["naturalness_table"]),
324
+ gr.update(value=texts["intelligibility_table"]),
325
+ gr.update(value=texts["similarity_table"]),
326
+ )
327
+
328
+ def check_submit_button(self, naturalness, intelligibility, similarity):
329
+ if naturalness is not None and intelligibility is not None and similarity is not None:
330
+ return gr.update(interactive=True)
331
+ else:
332
+ return gr.update(interactive=False)
333
+
334
+ def create_interface(self):
335
+
336
+ with gr.Blocks(theme='davehornik/Tealy', fill_width=True, title="MOS Survey") as demo:
337
+ def hello():
338
+ gr.Info("Hello! Please read the sidebar instructions carefully before starting the survey.")
339
+
340
+ demo.load(hello, inputs=[], outputs=[])
341
+
342
+ with gr.Sidebar(open=True, width=350):
343
+ sidebar_instructions = gr.Markdown(DESCRIPTIONS["Vietnamese"]["sidebar"])
344
+
345
+ state = gr.State(self.initialize_state())
346
+
347
+ with gr.Row():
348
+ with gr.Column(scale=5):
349
+ gr.Markdown("# Mean Opinion Score (MOS) Survey")
350
+ with gr.Column(scale=1):
351
+ language_toggle = gr.Checkbox(
352
+ label=DESCRIPTIONS["Vietnamese"]["language_toggle"],
353
+ value=False,
354
+ interactive=True,
355
+ )
356
+
357
+ gr.Markdown("------")
358
+
359
+ gr.Markdown("## Step 1. Enter your ID. If you have participated before, your progress will be restored.")
360
+
361
+ with gr.Row():
362
+ tester_id_input = gr.Textbox(
363
+ label="Enter Your ID", interactive=True
364
+ )
365
+ set_id_button = gr.Button("Set ID", interactive=False, variant="primary")
366
+ id_display = gr.Markdown()
367
+
368
+ # Enable/disable the Set ID button based on input.
369
+ def toggle_set_id_button(tester_id):
370
+
371
+ def check_valid_id(tester_id):
372
+ id = tester_id.strip()
373
+ if not id:
374
+ gr.Warning("Spaces are not allowed.", duration=5)
375
+ return False
376
+ if re.match(r"^[a-zA-Z0-9]+$", id):
377
+ return True
378
+ else:
379
+ gr.Warning("Only alphanumeric characters are allowed.", duration=5)
380
+ return False
381
+
382
+ return gr.update(interactive=check_valid_id(tester_id))
383
+
384
+ tester_id_input.change(
385
+ toggle_set_id_button,
386
+ inputs=[tester_id_input],
387
+ outputs=[set_id_button],
388
+ )
389
+
390
+ gr.Markdown("------")
391
+ gr.Markdown("## Step 2. Listen carefully to the following audio: ")
392
+
393
+ with gr.Row(equal_height=True):
394
+ with gr.Column(scale=2):
395
+ display_audio = gr.Audio(None, type="filepath", label="Synthesized Voice")
396
+
397
+ with gr.Column(scale=2):
398
+ gt_display_audio = gr.Audio(None, type="filepath", label="Reference Voice")
399
+
400
+ with gr.Column(scale=1):
401
+ progress_bar = gr.Slider(minimum=1, maximum=len(self.dfs[0]), value=0, label="Progress", interactive=False)
402
+ transcript_box = gr.Textbox(label="Ground-truth Transcript", interactive=False)
403
+
404
+ gr.Markdown("------")
405
+
406
+ gr.Markdown(
407
+ "## Step 3. Answer the following questions basing on the audio you hear.",
408
+ max_height=100
409
+ )
410
+
411
+ with gr.Row(equal_height=True):
412
+
413
+ with gr.Column():
414
+
415
+ gr.Markdown("### How natural is the above audio?")
416
+
417
+ with gr.Accordion("Evaluation Guidelines (Click to collapse/expand)", open=True):
418
+ naturalness_guide = gr.Markdown(
419
+ DESCRIPTIONS["Vietnamese"]["naturalness_guidelines"], max_height=100
420
+ )
421
+
422
+ naturalness_table = gr.Markdown(DESCRIPTIONS["Vietnamese"]["naturalness_table"])
423
+
424
+ naturalness = gr.Radio(
425
+ choices=[
426
+ "1 - Bad",
427
+ "1.5",
428
+ "2 - Poor",
429
+ "2.5",
430
+ "3 - Fair",
431
+ "3.5",
432
+ "4 - Good",
433
+ "4.5",
434
+ "5 - Excellent"
435
+ ],
436
+ value=None,
437
+ label="Naturalness Score",
438
+ interactive=False,
439
+ )
440
+
441
+ with gr.Column():
442
+ gr.Markdown("### How would you rate the intelligibility of the voice?")
443
+ with gr.Accordion("Evaluation Guidelines (Click to collapse/expand)", open=True):
444
+ intelligibility_guide = gr.Markdown(
445
+ DESCRIPTIONS["Vietnamese"]["intelligibility_guidelines"], max_height=100
446
+ )
447
+
448
+ intelligibility_table = gr.Markdown(
449
+ DESCRIPTIONS["Vietnamese"]["intelligibility_table"]
450
+ )
451
+
452
+ intelligibility = gr.Radio(
453
+ choices=[
454
+ "1 - Bad",
455
+ "1.5",
456
+ "2 - Poor",
457
+ "2.5",
458
+ "3 - Fair",
459
+ "3.5",
460
+ "4 - Good",
461
+ "4.5",
462
+ "5 - Excellent"
463
+ ],
464
+ value=None,
465
+ label="Intelligibility Score",
466
+ interactive=False,
467
+ )
468
+
469
+ with gr.Column():
470
+ gr.Markdown("### How similar are the speakers of the above two audio samples?")
471
+ with gr.Accordion("Evaluation Guidelines (Click to collapse/expand)", open=True):
472
+ similarity_guide = gr.Markdown(
473
+ DESCRIPTIONS["Vietnamese"]["similarity_guidelines"], max_height=100
474
+ )
475
+
476
+ similarity_table = gr.Markdown(DESCRIPTIONS["Vietnamese"]["similarity_table"])
477
+
478
+ similarity = gr.Radio(
479
+ choices=[
480
+ "1 - Bad",
481
+ "1.5",
482
+ "2 - Poor",
483
+ "2.5",
484
+ "3 - Fair",
485
+ "3.5",
486
+ "4 - Good",
487
+ "4.5",
488
+ "5 - Excellent"
489
+ ],
490
+ value=None,
491
+ label="Similarity Score",
492
+ interactive=False,
493
+ )
494
+
495
+ with gr.Row():
496
+ with gr.Column(scale=1):
497
+ back_btn = gr.Button("Back", interactive=False, variant="secondary")
498
+ with gr.Column(scale=2):
499
+ submit_btn = gr.Button("Submit", interactive=False, variant="primary")
500
+ with gr.Column(scale=1):
501
+ next_btn = gr.Button("Next", interactive=False, variant="secondary")
502
+
503
+ naturalness.change(
504
+ self.check_submit_button,
505
+ inputs=[naturalness, intelligibility, similarity],
506
+ outputs=[submit_btn],
507
+ )
508
+ intelligibility.change(
509
+ self.check_submit_button,
510
+ inputs=[naturalness, intelligibility, similarity],
511
+ outputs=[submit_btn],
512
+ )
513
+ similarity.change(
514
+ self.check_submit_button,
515
+ inputs=[naturalness, intelligibility, similarity],
516
+ outputs=[submit_btn],
517
+ )
518
+
519
+ # Navigation callbacks.
520
+ back_btn.click(
521
+ self.go_back,
522
+ inputs=[state],
523
+ outputs=[
524
+ display_audio,
525
+ transcript_box,
526
+ gt_display_audio,
527
+ naturalness,
528
+ intelligibility,
529
+ similarity,
530
+ state,
531
+ progress_bar,
532
+ back_btn,
533
+ next_btn,
534
+ ],
535
+ )
536
+ next_btn.click(
537
+ self.go_next,
538
+ inputs=[state],
539
+ outputs=[
540
+ display_audio,
541
+ transcript_box,
542
+ gt_display_audio,
543
+ naturalness,
544
+ intelligibility,
545
+ similarity,
546
+ state,
547
+ progress_bar,
548
+ back_btn,
549
+ next_btn,
550
+ ],
551
+ )
552
+
553
+ language_toggle.change(
554
+ self.toggle_language,
555
+ inputs=[language_toggle],
556
+ outputs=[
557
+ language_toggle,
558
+ sidebar_instructions,
559
+ naturalness_guide,
560
+ intelligibility_guide,
561
+ similarity_guide,
562
+ naturalness_table,
563
+ intelligibility_table,
564
+ similarity_table,
565
+ ]
566
+ )
567
+ set_id_button.click(
568
+ self.set_tester_id,
569
+ inputs=[tester_id_input, state],
570
+ outputs=[
571
+ id_display,
572
+ state,
573
+ display_audio,
574
+ transcript_box,
575
+ gt_display_audio,
576
+ tester_id_input,
577
+ set_id_button,
578
+ submit_btn,
579
+ naturalness,
580
+ intelligibility,
581
+ similarity,
582
+ progress_bar,
583
+ ],
584
+ )
585
+ submit_btn.click(
586
+ self.submit_options,
587
+ inputs=[naturalness, intelligibility, similarity, state],
588
+ outputs=[
589
+ display_audio,
590
+ naturalness,
591
+ intelligibility,
592
+ similarity,
593
+ state,
594
+ transcript_box,
595
+ gt_display_audio,
596
+ progress_bar,
597
+ back_btn,
598
+ next_btn,
599
+ ],
600
+ )
601
+
602
+ return demo
603
+
604
+
605
+ if __name__ == "__main__":
606
+ port = int(os.environ.get("PORT", 5001))
607
+ current_date = datetime.now().strftime("%Y%m%d")
608
+ app = MOSApp(
609
+ dirpath="./samples/data", # change as you need
610
+ outdir=f"./results/{current_date}",
611
+ progress_dir=f"./progress/{current_date}",
612
+ )
613
+ demo = app.create_interface()
614
+ demo.launch(share=True)
615
+ # demo.launch(server_name="0.0.0.0", server_port=port)
display_text.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ DESCRIPTIONS = {
2
+ "English": {
3
+ "sidebar": """
4
+ ## MOS Survey Instructions
5
+
6
+ ------
7
+
8
+ **What is MOS?**
9
+
10
+ MOS (Mean Opinion Score) is a simple method to rate audio quality. In this survey, you'll listen to speech samples and rate them based on:
11
+
12
+ 1. **Naturalness:** How natural is the generated speech compared to actual human speech?
13
+ 2. **Intelligibility:** How clearly are the words in the generated speech compared to the actual transcription?
14
+ 3. **Similarity:** How closely does the synthesized speech match a real (reference) voice?
15
+
16
+ ------
17
+
18
+ **How to Rate:**
19
+
20
+ - **Step 1:** Enter your ID. The **Set ID** button becomes clickable when you enter a valid ID.
21
+ - **Step 2:** Listen to the audio sample.
22
+ - **Step 3:** Use the radio buttons to rate each aspect.
23
+ - **Step 4:** Click **Submit** to save your rating and move to the next sample.
24
+
25
+ ------
26
+
27
+ **Additional Tips:**
28
+ - Use headphones in a quiet environment for best audio evaluation.
29
+ - Only "Submit" button can save your rating. "Back" and "Next" buttons can't.
30
+
31
+ ------
32
+
33
+ ***Thank you for participating in our MOS survey!***
34
+ """,
35
+ "naturalness_guidelines": """
36
+ <div style="font-size: 16px;">
37
+ Listen to the speech sample and assess the quality of the audio based on <u><b>how close it is to natural speech</b></u> on a scale of 1 to 5.
38
+ </div>
39
+ """,
40
+ "intelligibility_guidelines": """
41
+ <div style="font-size: 16px;">
42
+ Listen to the speech sample and assess <u><b>how clearly and accurately the spoken content is conveyed</b></u> on a scale of 1 to 5. Your rating should reflect how easily the words can be understood compared to the provided transcription. You <b style='color:red;'>should NOT</b> judge the naturalness of the voice or the speaker’s identity. Instead, focus solely on the clarity of the spoken words. <b>If there is background noise (e.g., static, hiss, or other unwanted artifacts) that affects your ability to understand the words, factor this into your evaluation.</b>
43
+ </div>
44
+ """,
45
+ "similarity_guidelines": """
46
+ <div style="font-size: 16px;">
47
+ Listen to the speech samples and rate the <u><b>speaker similarity</b></u> between them on a scale of 1 to 5. Your rating should reflect your evaluation of how close the voices of the two speakers sound. You <b style='color:red;'>should NOT</b> judge the accent, content, grammar, expressiveness, or audio quality of the two voices. Instead, just focus on the similarity of the speakers to one another.
48
+ </div>
49
+ """,
50
+ "naturalness_table": """
51
+ | Score | Description |
52
+ |-------------------|-------------------------------------------------------------------|
53
+ | **5 - Excellent** | Speech sounds completely natural and like a real person. |
54
+ | **4 - Good** | Mostly natural with minor robotic artifacts. |
55
+ | **3 - Fair** | A mix of natural and artificial characteristics. |
56
+ | **2 - Poor** | Mostly unnatural with noticeable robotic cues. |
57
+ | **1 - Bad** | Completely artificial and very robotic. |
58
+ """,
59
+ "intelligibility_table": """
60
+ | Score | Description |
61
+ |-------------------|---------------------------------------|
62
+ | **5 - Excellent** | Perfect understanding without any effort. |
63
+ | **4 - Good** | Easy to understand with minimal effort. |
64
+ | **3 - Fair** | Understandable with some effort. |
65
+ | **2 - Poor** | Difficult to understand. |
66
+ | **1 - Bad** | Impossible to understand. |
67
+ """,
68
+ "similarity_table": """
69
+ | Score | Description |
70
+ |-------------------|---------------------------------------|
71
+ | **5 - Excellent** | Sounds exactly like the reference voice. |
72
+ | **4 - Good** | Very similar to the reference voice. |
73
+ | **3 - Fair** | Somewhat similar to the reference voice. |
74
+ | **2 - Poor** | Different from the reference voice. |
75
+ | **1 - Bad** | Completely different from the reference voice.|
76
+ """,
77
+ "language_toggle": "Uncheck to see Vietnamese Guidelines"
78
+ },
79
+ "Vietnamese": {
80
+ "sidebar": """
81
+ ## Hướng dẫn đánh giá MOS
82
+
83
+ ------
84
+
85
+ **MOS là gì?**
86
+
87
+ MOS (Mean Opinion Score) là phương pháp đơn giản để đánh giá chất lượng âm thanh. Trong khảo sát này, bạn sẽ nghe các mẫu giọng nói và chấm điểm trên các tiêu chí sau:
88
+
89
+ 1. **Độ tự nhiên:** Giọng nói được tạo ra có tự nhiên như giọng nói con người không?
90
+ 2. **Độ rõ ràng:** Các từ trong giọng nói được tạo ra có dễ nghe và chính xác so với bản phiên âm không?
91
+ 3. **Độ giống nhau:** Giọng nói tổng hợp có giống với giọng nói gốc không?
92
+
93
+ ------
94
+
95
+ **Cách đánh giá:**
96
+
97
+ - **Bước 1:** Nhập ID của bạn. Nút **Set ID** sẽ click được khi bạn nhập ID hợp lệ.
98
+ - **Bước 2:** Nghe mẫu âm thanh.
99
+ - **Bước 3:** Sử dụng các nút chọn để chấm điểm cho từng tiêu chí.
100
+ - **Bước 4:** Nhấn **Submit** để lưu đánh giá và chuyển sang mẫu tiếp theo.
101
+
102
+ ------
103
+
104
+ **Mẹo:**
105
+ - Hãy sử dụng tai nghe và chọn không gian yên tĩnh để có đánh giá chính xác nhất.
106
+ - Chỉ có nút "Submit" mới có thể lưu đánh giá của bạn. Nút "Back" và "Next" không thể.
107
+
108
+ ------
109
+
110
+ ***Cảm ơn bạn đã tham gia khảo sát MOS của tụi mình!***
111
+ """,
112
+ "naturalness_guidelines": """
113
+ <div style="font-size: 16px;">
114
+ Hãy nghe đoạn ghi âm và đánh giá chất lượng giọng nói dựa trên <u><b>mức độ tự nhiên</b></u> của nó (giống người thật hay không) theo thang điểm từ 1 đến 5.
115
+ </div>
116
+ """,
117
+ "intelligibility_guidelines": """
118
+ <div style="font-size: 16px;">
119
+ Hãy nghe mẫu giọng nói và đánh giá <u><b>mức độ rõ ràng, chính xác của nội dung được truyền tải</b></u> theo thang điểm từ 1 đến 5. Điểm số của bạn nên phản ánh mức độ dễ hiểu của lời nói so với bản phiên âm được cung cấp, nghĩa là độ dễ dàng để nhận ra các từ được nói là các từ trong bản phiên âm. Bạn <b style='color:red;'>KHÔNG NÊN</b> đánh giá về sự tự nhiên của giọng nói hay giới tính/danh tính của người nói. Thay vào đó, hãy chỉ tập trung vào độ rõ ràng của các từ được nói ra. <b>Nếu có tạp âm (ví dụ: nhiễu, tiếng rè, hoặc các âm thanh không mong muốn khác) làm ảnh hưởng đến khả năng nghe hiểu, hãy cân nhắc đến cả yếu tố này khi chấm điểm.</b>
120
+ </div>
121
+ """,
122
+ "similarity_guidelines": """
123
+ <div style="font-size: 16px;">
124
+ Hãy nghe hai đoạn âm thanh và đánh giá <u><b>mức độ giống nhau giữa hai giọng nói</b></u> theo thang điểm từ 1 đến 5. Điểm số của bạn nên phản ánh mức độ giống nhau của giọng nói giữa hai người trong hai đoạn âm thanh. Bạn <b style='color:red;'>KHÔNG NÊN</b> đánh giá về giọng điệu (accent), nội dung, ngữ pháp, cảm xúc, tốc độ nói, hay chất lượng âm thanh. Thay vào đó, hãy chỉ tập trung vào việc hai giọng nói có giống nhau hay không.
125
+ </div>
126
+ """,
127
+ "naturalness_table": """
128
+ | Điểm | Mô tả |
129
+ |--------------------|-------------------------------------------------------------------------|
130
+ | **5 - Excellent** | Giọng nói nghe hoàn toàn tự nhiên, giống như một người thật. |
131
+ | **4 - Good** | Hầu như tự nhiên, chỉ có chút âm thanh máy móc không đáng kể. |
132
+ | **3 - Fair** | Kết hợp giữa đặc điểm tự nhiên và nhân tạo. |
133
+ | **2 - Poor** | Chủ yếu nghe không tự nhiên, có nhiều dấu hiệu giọng máy. |
134
+ | **1 - Bad** | Hoàn toàn nhân tạo, nghe rõ ràng như giọng robot. |
135
+ """,
136
+ "intelligibility_table": """
137
+ | Điểm | Mô tả |
138
+ |--------------------|---------------------------------------------------------|
139
+ | **5 - Excellent** | Hoàn toàn hiểu được mà không cần bất kỳ sự cố gắng nào. |
140
+ | **4 - Good** | Dễ hiểu, chỉ cần chú ý một chút. |
141
+ | **3 - Fair** | Có thể hiểu nhưng phải tập trung hơn. |
142
+ | **2 - Poor** | Khó nghe, khó hiểu. |
143
+ | **1 - Bad** | Không thể hiểu được. |
144
+ """,
145
+ "similarity_table": """
146
+ | Điểm | Mô tả |
147
+ |--------------------|----------------------------------------------|
148
+ | **5 - Excellent** | Giống hệt giọng tham chiếu. |
149
+ | **4 - Good** | Rất giống, chỉ có chút khác biệt nhỏ. |
150
+ | **3 - Fair** | Có nét giống nhưng vẫn khác tương đối. |
151
+ | **2 - Poor** | Nghe khác khá nhiều so với giọng tham chiếu. |
152
+ | **1 - Bad** | Không giống chút nào với giọng tham chiếu. |
153
+ """,
154
+ "language_toggle": "Click chọn để xem hướng dẫn Tiếng Anh"
155
+ }
156
+ }
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio==5.15.0
2
+ pandas==2.2.3