Spaces:
Running
Running
Initiation
Browse files- .gitattributes +7 -0
- README.md +39 -4
- README_INSTRUCTIONS.md +37 -0
- Rubric for choosing a TTS voice.docx +0 -0
- app.py +430 -0
- config_mos.yaml +37 -0
- config_original.yaml +93 -0
- download_dataset.py +94 -0
- requirements.txt +8 -0
- sample-audios/sorsamisk_-_115_01_-_Goevten_voestes_biejjie_015_024_s.wav +3 -0
- sample-audios/sorsamisk_-_7_01_-_Jarkoestidh_028_020.wav +3 -0
- sample-audios/sorsamisk_-_III_01_-_Giesie_eejehtimmiebiejjieh_015_015.wav +3 -0
- sample-audios/sorsamisk_goltelidh_jupmelen_rihjke_lea_gietskesne_cd1_mono_022_009.wav +3 -0
- sample-audios/sorsamisk_goltelidh_jupmelen_rihjke_lea_gietskesne_cd1_mono_030_014.wav +3 -0
- sample-audios/sorsamisk_goltelidh_jupmelen_rihjke_lea_gietskesne_cd2_009_TRN_019.wav +3 -0
- sample-audios/sorsamisk_goltelidh_jupmelen_rihjke_lea_gietskesne_cd2_009_TRN_019_s.wav +3 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,10 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
sample-audios/sorsamisk_-_115_01_-_Goevten_voestes_biejjie_015_024_s.wav filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
sample-audios/sorsamisk_-_7_01_-_Jarkoestidh_028_020.wav filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
sample-audios/sorsamisk_-_III_01_-_Giesie_eejehtimmiebiejjieh_015_015.wav filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
sample-audios/sorsamisk_goltelidh_jupmelen_rihjke_lea_gietskesne_cd1_mono_022_009.wav filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
sample-audios/sorsamisk_goltelidh_jupmelen_rihjke_lea_gietskesne_cd1_mono_030_014.wav filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
sample-audios/sorsamisk_goltelidh_jupmelen_rihjke_lea_gietskesne_cd2_009_TRN_019_s.wav filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
sample-audios/sorsamisk_goltelidh_jupmelen_rihjke_lea_gietskesne_cd2_009_TRN_019.wav filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -1,12 +1,47 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 5.49.1
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
|
|
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Tts Online Rubric
|
| 3 |
+
emoji: 🌍
|
| 4 |
+
colorFrom: indigo
|
| 5 |
+
colorTo: red
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 5.49.1
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
+
license: cc-by-4.0
|
| 11 |
+
short_description: A rubric for choosing a good TTS voice
|
| 12 |
---
|
| 13 |
|
| 14 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
| 15 |
+
|
| 16 |
+
## Saving responses to a private Hugging Face dataset
|
| 17 |
+
|
| 18 |
+
This project can optionally push saved responses to a private dataset on the Hugging Face Hub. The feature is disabled by default and only active when the following environment variables are set:
|
| 19 |
+
|
| 20 |
+
- `HF_TOKEN` — a Hugging Face access token with dataset write permissions.
|
| 21 |
+
- `HF_DATASET_ID` — repo id like `your-username/my-eval-responses`.
|
| 22 |
+
|
| 23 |
+
Setup:
|
| 24 |
+
|
| 25 |
+
1. Create a token on https://huggingface.co/settings/tokens and grant it write permissions for datasets/repos.
|
| 26 |
+
2. Export the variables locally before running the app:
|
| 27 |
+
|
| 28 |
+
```bash
|
| 29 |
+
export HF_TOKEN="hf_...your_token..."
|
| 30 |
+
export HF_DATASET_ID="your-username/my-eval-responses"
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
3. Install dependencies and run the app:
|
| 34 |
+
|
| 35 |
+
```bash
|
| 36 |
+
pip install -r requirements.txt
|
| 37 |
+
python app.py
|
| 38 |
+
# open http://localhost:7860 and submit ratings via the UI
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
Notes:
|
| 42 |
+
|
| 43 |
+
- If the `datasets` or `huggingface_hub` packages are not installed, HF pushing is skipped gracefully and responses are still written to `responses.csv`.
|
| 44 |
+
- Pushing audio files will use Git LFS on the Hub; monitor your account storage and LFS quota.
|
| 45 |
+
- Each UI save triggers a small push (single-record commit). For heavy usage consider batching pushes instead.
|
| 46 |
+
|
| 47 |
+
There is also a small test script included to programmatically verify pushing a single sample row to your dataset. See `hf_push_test.py`.
|
README_INSTRUCTIONS.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# TTS Online Rubric (Gradio)
|
| 2 |
+
|
| 3 |
+
This is a minimal Gradio-based UI for running TTS rubric evaluations. It's designed to be deployed on Hugging Face Spaces or run locally.
|
| 4 |
+
|
| 5 |
+
Features:
|
| 6 |
+
- Browse audio files from `sample-audios/`.
|
| 7 |
+
- Play reference audio and upload system outputs to compare.
|
| 8 |
+
- Rate outputs on three sliders (nativeness, naturalness, overall quality) and add comments.
|
| 9 |
+
- Responses are appended to `responses.csv`.
|
| 10 |
+
|
| 11 |
+
How to run locally:
|
| 12 |
+
|
| 13 |
+
1. Create a Python environment and install requirements:
|
| 14 |
+
|
| 15 |
+
```bash
|
| 16 |
+
python -m venv .venv
|
| 17 |
+
source .venv/bin/activate
|
| 18 |
+
pip install -r requirements.txt
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
2. Add your reference audio files under `sample-audios/`.
|
| 22 |
+
|
| 23 |
+
3. Run the app:
|
| 24 |
+
|
| 25 |
+
```bash
|
| 26 |
+
python app.py
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
4. Open the URL shown in the terminal (default http://127.0.0.1:7860).
|
| 30 |
+
|
| 31 |
+
Deploying to Hugging Face Spaces:
|
| 32 |
+
|
| 33 |
+
- Create a new Space using the Gradio SDK (python) and push this repo. Ensure `requirements.txt` is present. The app will run automatically.
|
| 34 |
+
|
| 35 |
+
Notes and next steps:
|
| 36 |
+
- The current UI accepts system outputs via file upload. You may prefer a fixed set of system files stored in a directory for side-by-side playback.
|
| 37 |
+
- Consider adding authentication or locking to avoid duplicate/corrupt CSV writes under heavy load.
|
Rubric for choosing a TTS voice.docx
ADDED
|
Binary file (9.11 kB). View file
|
|
|
app.py
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
|
| 3 |
+
import gradio as gr
|
| 4 |
+
import os
|
| 5 |
+
import csv
|
| 6 |
+
import fcntl
|
| 7 |
+
from datetime import datetime
|
| 8 |
+
import uuid
|
| 9 |
+
import yaml # You need to install this: pip install pyyaml
|
| 10 |
+
import glob
|
| 11 |
+
import random
|
| 12 |
+
import json
|
| 13 |
+
import pandas as pd
|
| 14 |
+
import io
|
| 15 |
+
|
| 16 |
+
# --- Hugging Face Functionality Notes ---
|
| 17 |
+
# To save results to a private Hugging Face dataset, you must:
|
| 18 |
+
# 1. Install the required libraries: pip install huggingface_hub datasets
|
| 19 |
+
# 2. Set the following environment variables before running the script:
|
| 20 |
+
# - HF_TOKEN: Your Hugging Face access token with write permissions.
|
| 21 |
+
# - HF_DATASET_ID: The ID of the private dataset repo (e.g., "username/my-dataset").
|
| 22 |
+
# If these are not set, saving to HF Hub will be skipped.
|
| 23 |
+
|
| 24 |
+
# --- Start of Local Mode Implementation ---
|
| 25 |
+
IS_LOCAL_MODE = os.environ.get("GRADIO_LOCAL_MODE", "false").lower() in ["true", "1"]
|
| 26 |
+
|
| 27 |
+
if IS_LOCAL_MODE:
|
| 28 |
+
print("Running in LOCAL mode. Hugging Face functionalities are disabled.")
|
| 29 |
+
HfApi = None
|
| 30 |
+
else:
|
| 31 |
+
try:
|
| 32 |
+
from huggingface_hub import HfApi, hf_hub_download
|
| 33 |
+
print("Hugging Face libraries found. HF push functionality is available.")
|
| 34 |
+
except ImportError:
|
| 35 |
+
print("Hugging Face libraries not found. HF push functionality will be disabled.")
|
| 36 |
+
HfApi = None
|
| 37 |
+
# --- End of Local Mode Implementation ---
|
| 38 |
+
|
| 39 |
+
# --- Configuration Loading ---
|
| 40 |
+
def load_config(config_path='config.yaml'):
|
| 41 |
+
"""Loads the UI and criteria configuration from a YAML file."""
|
| 42 |
+
try:
|
| 43 |
+
with open(config_path, 'r', encoding='utf-8') as f:
|
| 44 |
+
config = yaml.safe_load(f)
|
| 45 |
+
if 'criteria' not in config or not isinstance(config['criteria'], list):
|
| 46 |
+
raise ValueError("Config must contain a list of 'criteria'.")
|
| 47 |
+
return config
|
| 48 |
+
except FileNotFoundError:
|
| 49 |
+
return None
|
| 50 |
+
except Exception as e:
|
| 51 |
+
print(f"ERROR: Could not parse {config_path}: {e}")
|
| 52 |
+
return None
|
| 53 |
+
|
| 54 |
+
def find_config_files():
|
| 55 |
+
"""Finds all .yaml and .yml files in the root directory."""
|
| 56 |
+
return glob.glob("*.yaml") + glob.glob("*.yml")
|
| 57 |
+
|
| 58 |
+
# --- Static & File I/O Functions ---
|
| 59 |
+
OUTPUT_CSV = "responses.csv"
|
| 60 |
+
MAX_CRITERIA = 15 # Maximum number of sliders to support
|
| 61 |
+
|
| 62 |
+
def list_samples(samples_dir):
|
| 63 |
+
"""Lists audio files from a specified directory."""
|
| 64 |
+
if not os.path.isdir(samples_dir):
|
| 65 |
+
print(f"WARNING: Samples directory '{samples_dir}' not found.")
|
| 66 |
+
return []
|
| 67 |
+
files = [f for f in os.listdir(samples_dir) if f.lower().endswith(('.wav', '.mp3', '.ogg', '.flac'))]
|
| 68 |
+
files.sort()
|
| 69 |
+
return files
|
| 70 |
+
|
| 71 |
+
def save_responses_to_hf(rows, repo_id: str | None = None, token: str | None = None):
|
| 72 |
+
"""
|
| 73 |
+
Append new rows to a CSV file in a private Hugging Face dataset.
|
| 74 |
+
|
| 75 |
+
- Reads the existing CSV (if present).
|
| 76 |
+
- Appends new rows.
|
| 77 |
+
- Uploads the updated file back to the repo.
|
| 78 |
+
|
| 79 |
+
Each 'row' should be a dict with consistent keys.
|
| 80 |
+
|
| 81 |
+
NOTE:
|
| 82 |
+
- Replaces the entire CSV on each update (no true append on the server side).
|
| 83 |
+
- Use small/medium datasets; large ones should use the `datasets` library instead.
|
| 84 |
+
"""
|
| 85 |
+
if HfApi is None:
|
| 86 |
+
return {"status": "hf_unavailable", "reason": "missing_packages"}
|
| 87 |
+
|
| 88 |
+
token = token or os.environ.get("HF_TOKEN")
|
| 89 |
+
repo_id = repo_id or os.environ.get("HF_DATASET_ID")
|
| 90 |
+
if not token or not repo_id:
|
| 91 |
+
return {"status": "hf_skipped", "reason": "missing_token_or_repo_env"}
|
| 92 |
+
|
| 93 |
+
api = HfApi(token=token)
|
| 94 |
+
path_in_repo = "data/responses.csv" # fixed CSV location in repo
|
| 95 |
+
repo_err = None
|
| 96 |
+
|
| 97 |
+
# Ensure dataset exists
|
| 98 |
+
try:
|
| 99 |
+
api.create_repo(repo_id=repo_id, repo_type="dataset", private=True, exist_ok=True)
|
| 100 |
+
except Exception as e:
|
| 101 |
+
repo_err = str(e)
|
| 102 |
+
|
| 103 |
+
# Try downloading existing CSV
|
| 104 |
+
existing_df = pd.DataFrame()
|
| 105 |
+
try:
|
| 106 |
+
local_path = hf_hub_download(
|
| 107 |
+
repo_id=repo_id,
|
| 108 |
+
filename=path_in_repo,
|
| 109 |
+
repo_type="dataset",
|
| 110 |
+
token=token,
|
| 111 |
+
)
|
| 112 |
+
existing_df = pd.read_csv(local_path)
|
| 113 |
+
except Exception as e:
|
| 114 |
+
print("file", path_in_repo, "couldn't be found / read", str(e))
|
| 115 |
+
# File doesn't exist or is unreadable — start fresh
|
| 116 |
+
pass
|
| 117 |
+
|
| 118 |
+
# Convert new rows to DataFrame and append
|
| 119 |
+
new_df = pd.DataFrame(rows)
|
| 120 |
+
combined_df = pd.concat([existing_df, new_df], ignore_index=True)
|
| 121 |
+
print(combined_df)
|
| 122 |
+
# Save to memory as CSV
|
| 123 |
+
csv_buffer = io.StringIO()
|
| 124 |
+
combined_df.to_csv(csv_buffer, index=False)
|
| 125 |
+
csv_bytes = csv_buffer.getvalue().encode("utf-8")
|
| 126 |
+
|
| 127 |
+
# Upload the updated CSV
|
| 128 |
+
try:
|
| 129 |
+
api.upload_file(
|
| 130 |
+
path_or_fileobj=csv_bytes,
|
| 131 |
+
path_in_repo=path_in_repo,
|
| 132 |
+
repo_id=repo_id,
|
| 133 |
+
repo_type="dataset",
|
| 134 |
+
)
|
| 135 |
+
except Exception as e:
|
| 136 |
+
print(str(e))
|
| 137 |
+
return {"status": "hf_push_error", "error": str(e), "repo_error": repo_err}
|
| 138 |
+
|
| 139 |
+
return {"status": "hf_pushed", "rows_added": len(rows), "repo": repo_id, "repo_error": repo_err}
|
| 140 |
+
|
| 141 |
+
def _save_responses_to_hf(rows, repo_id: str | None = None, token: str | None = None):
|
| 142 |
+
"""
|
| 143 |
+
Push a list of dict rows to a private HF dataset, one JSON file per row.
|
| 144 |
+
|
| 145 |
+
NOTE: This approach saves each response as an individual file. While this
|
| 146 |
+
prevents data loss from overwriting a single file, be aware of the following:
|
| 147 |
+
- Performance: Uploading many small files can be slower than a single large one.
|
| 148 |
+
- Scalability: A very large number of files (e.g., millions) can make the
|
| 149 |
+
dataset repository unwieldy to browse or clone.
|
| 150 |
+
- Loading Data: To load this data back into a `datasets.Dataset` object, you
|
| 151 |
+
will need to point to the specific files, for example:
|
| 152 |
+
`load_dataset('json', data_files='path/to/your/repo/data/*.json')`
|
| 153 |
+
"""
|
| 154 |
+
if HfApi is None:
|
| 155 |
+
return {"status": "hf_unavailable", "reason": "missing_packages"}
|
| 156 |
+
|
| 157 |
+
token = token or os.environ.get("HF_TOKEN")
|
| 158 |
+
repo_id = repo_id or os.environ.get("HF_DATASET_ID")
|
| 159 |
+
if not token or not repo_id:
|
| 160 |
+
return {"status": "hf_skipped", "reason": "missing_token_or_repo_env"}
|
| 161 |
+
|
| 162 |
+
api = HfApi(token=token)
|
| 163 |
+
repo_err = None
|
| 164 |
+
try:
|
| 165 |
+
api.create_repo(repo_id=repo_id, repo_type="dataset", private=True, exist_ok=True)
|
| 166 |
+
except Exception as e:
|
| 167 |
+
repo_err = str(e)
|
| 168 |
+
|
| 169 |
+
# Process each row, uploading it as a separate JSON file
|
| 170 |
+
num_pushed = 0
|
| 171 |
+
errors = []
|
| 172 |
+
for row_dict in rows:
|
| 173 |
+
try:
|
| 174 |
+
# Create a unique filename. Using a UUID is the most robust method.
|
| 175 |
+
filename = f"{uuid.uuid4()}.json"
|
| 176 |
+
# Place files in a 'data' subdirectory to keep the repo root clean.
|
| 177 |
+
path_in_repo = f"data/{filename}"
|
| 178 |
+
|
| 179 |
+
# Convert the dictionary to JSON bytes for uploading
|
| 180 |
+
json_bytes = json.dumps(row_dict, indent=2).encode("utf-8")
|
| 181 |
+
|
| 182 |
+
api.upload_file(
|
| 183 |
+
path_or_obj=json_bytes,
|
| 184 |
+
path_in_repo=path_in_repo,
|
| 185 |
+
repo_id=repo_id,
|
| 186 |
+
repo_type="dataset",
|
| 187 |
+
)
|
| 188 |
+
num_pushed += 1
|
| 189 |
+
except Exception as e:
|
| 190 |
+
errors.append(str(e))
|
| 191 |
+
|
| 192 |
+
if errors:
|
| 193 |
+
print("json errors", errors, "repo errors", repo_err)
|
| 194 |
+
return {"status": "hf_push_error", "pushed": num_pushed, "total": len(rows), "errors": errors, "repo_error": repo_err}
|
| 195 |
+
|
| 196 |
+
return {"status": "hf_pushed", "rows": len(rows), "repo": repo_id, "repo_error": repo_err}
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def save_response(sample, audio_path, annotator, session_id, user_email, comment, scores, config):
|
| 200 |
+
"""Saves a response row locally and attempts to push to Hugging Face Hub."""
|
| 201 |
+
os.makedirs(os.path.dirname(OUTPUT_CSV) or '.', exist_ok=True)
|
| 202 |
+
|
| 203 |
+
criteria_labels = [c['label'] for c in config['criteria']]
|
| 204 |
+
header = ["timestamp", "sample", "audio_path", "annotator", "session_id", "user_email"] + criteria_labels + ["comment"]
|
| 205 |
+
|
| 206 |
+
active_scores = list(scores)[:len(criteria_labels)]
|
| 207 |
+
row = [datetime.utcnow().isoformat(), sample, audio_path, annotator, session_id, user_email] + active_scores + [comment]
|
| 208 |
+
|
| 209 |
+
write_header = not os.path.exists(OUTPUT_CSV)
|
| 210 |
+
with open(OUTPUT_CSV, "a", newline='', encoding='utf-8') as f:
|
| 211 |
+
try: fcntl.flock(f.fileno(), fcntl.LOCK_EX)
|
| 212 |
+
except Exception: pass
|
| 213 |
+
|
| 214 |
+
writer = csv.writer(f)
|
| 215 |
+
if write_header: writer.writerow(header)
|
| 216 |
+
writer.writerow(row)
|
| 217 |
+
|
| 218 |
+
try: fcntl.flock(f.fileno(), fcntl.LOCK_UN)
|
| 219 |
+
except Exception: pass
|
| 220 |
+
|
| 221 |
+
# --- Hugging Face Push Logic ---
|
| 222 |
+
hf_result = None
|
| 223 |
+
if not IS_LOCAL_MODE:
|
| 224 |
+
try:
|
| 225 |
+
hf_record = dict(zip(header, row))
|
| 226 |
+
hf_result = save_responses_to_hf([hf_record])
|
| 227 |
+
except Exception as e:
|
| 228 |
+
print(e)
|
| 229 |
+
hf_result = {"status": "hf_error", "error": str(e)}
|
| 230 |
+
|
| 231 |
+
return {"status": "saved", "sample": sample, "hf": hf_result}
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
# --- Gradio UI Definition ---
|
| 235 |
+
def make_ui():
|
| 236 |
+
|
| 237 |
+
def make_explainer_fn(criterion_index):
|
| 238 |
+
def explainer(value, config):
|
| 239 |
+
if not config or criterion_index >= len(config.get('criteria', [])): return ""
|
| 240 |
+
criterion = config['criteria'][criterion_index]
|
| 241 |
+
try: iv = int(value)
|
| 242 |
+
except (ValueError, TypeError): iv = value
|
| 243 |
+
text = criterion['explanations'].get(iv, "No description for this score.")
|
| 244 |
+
return f"**{criterion['label']} ({iv}/{criterion['max']}):** {text}"
|
| 245 |
+
return explainer
|
| 246 |
+
|
| 247 |
+
#with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as demo:
|
| 248 |
+
with gr.Blocks() as demo:
|
| 249 |
+
# --- STATE MANAGEMENT ---
|
| 250 |
+
samples_list = gr.State()
|
| 251 |
+
current_index = gr.State(0)
|
| 252 |
+
config_state = gr.State()
|
| 253 |
+
session_id_global = gr.State()
|
| 254 |
+
|
| 255 |
+
# --- SETUP UI (Visible at start) ---
|
| 256 |
+
with gr.Group() as setup_group:
|
| 257 |
+
gr.Markdown("# Evaluation Setup")
|
| 258 |
+
gr.Markdown("Please provide your details and select the evaluation setup to begin.")
|
| 259 |
+
#config_dropdown = gr.Dropdown(choices=find_config_files(), label="Select Evaluation", value=find_config_files()[0] if find_config_files() else "")
|
| 260 |
+
config_dropdown = gr.Dropdown(choices=find_config_files(), label="Select Evaluation", value=None) # if find_config_files() else "")
|
| 261 |
+
|
| 262 |
+
instructions_md = gr.Markdown(visible=False, elem_classes="instructions")
|
| 263 |
+
|
| 264 |
+
with gr.Accordion("Annotator Info", open=True):
|
| 265 |
+
annotator_global = gr.Textbox(label="Annotator ID", lines=1)
|
| 266 |
+
user_email_global = gr.Textbox(label="User email (optional)", lines=1)
|
| 267 |
+
start_button = gr.Button("Start Evaluation", variant="primary")
|
| 268 |
+
config_error_md = gr.Markdown("", visible=False)
|
| 269 |
+
|
| 270 |
+
# --- MAIN EVALUATION UI (Initially hidden) ---
|
| 271 |
+
with gr.Group(visible=False) as main_group:
|
| 272 |
+
title_md = gr.Markdown("# Evaluation UI")
|
| 273 |
+
header_md = gr.Markdown("")
|
| 274 |
+
progress_md = gr.Markdown("Sample 1 of X")
|
| 275 |
+
|
| 276 |
+
with gr.Row():
|
| 277 |
+
with gr.Column(scale=1, variant='panel'):
|
| 278 |
+
sample_name_md = gr.Markdown("### Audio File")
|
| 279 |
+
gr.Markdown("---")
|
| 280 |
+
evaluation_audio = gr.Audio(label="Audio for Evaluation")
|
| 281 |
+
gr.Markdown("---")
|
| 282 |
+
submit_btn = gr.Button("Save & Next", variant="primary", interactive=False)
|
| 283 |
+
status = gr.Textbox(label="Status", interactive=False)
|
| 284 |
+
|
| 285 |
+
with gr.Column(scale=2, variant='panel'):
|
| 286 |
+
gr.Markdown("### Scoring Criteria")
|
| 287 |
+
slider_explanation_md = gr.Markdown("_Move a slider to see the description for each score._")
|
| 288 |
+
gr.Markdown("---")
|
| 289 |
+
sliders = [gr.Slider(visible=False, interactive=True) for _ in range(MAX_CRITERIA)]
|
| 290 |
+
gr.Markdown("---")
|
| 291 |
+
comment = gr.Textbox(label="Comments (optional)", lines=4, placeholder="Enter any additional feedback here...")
|
| 292 |
+
|
| 293 |
+
# --- UI ELEMENT LISTS ---
|
| 294 |
+
main_ui_elements = [
|
| 295 |
+
title_md, header_md, progress_md, sample_name_md, evaluation_audio,
|
| 296 |
+
slider_explanation_md, comment, submit_btn, status, *sliders
|
| 297 |
+
]
|
| 298 |
+
|
| 299 |
+
# --- LOGIC & EVENTS ---
|
| 300 |
+
def load_sample(samples, index, config):
|
| 301 |
+
total_samples = len(samples)
|
| 302 |
+
updates = {}
|
| 303 |
+
if index >= total_samples:
|
| 304 |
+
completion_msg = f"**All {total_samples} samples completed! Thank you!**"
|
| 305 |
+
for el in main_ui_elements: updates[el] = gr.update(visible=False)
|
| 306 |
+
updates[progress_md] = gr.update(value=completion_msg, visible=True)
|
| 307 |
+
updates[status] = gr.update(value="Finished.", visible=True)
|
| 308 |
+
return updates
|
| 309 |
+
|
| 310 |
+
sample = samples[index]
|
| 311 |
+
samples_dir = config.get('samples_directory', 'sample-audios')
|
| 312 |
+
sample_path = os.path.join(samples_dir, sample)
|
| 313 |
+
sample_exists = os.path.exists(sample_path)
|
| 314 |
+
|
| 315 |
+
updates = {
|
| 316 |
+
progress_md: gr.update(value=f"Sample **{index + 1}** of **{total_samples}**", visible=True),
|
| 317 |
+
sample_name_md: gr.update(value=f"### File: `{sample}`", visible=True),
|
| 318 |
+
evaluation_audio: gr.update(value=sample_path if sample_exists else None, visible=sample_exists),
|
| 319 |
+
slider_explanation_md: gr.update(value="_Move a slider to see the description for each score._", visible=True),
|
| 320 |
+
comment: gr.update(value="", visible=True),
|
| 321 |
+
submit_btn: gr.update(value="Play audio to enable", interactive=False, visible=True),
|
| 322 |
+
status: gr.update(value="Ready.", visible=True)
|
| 323 |
+
}
|
| 324 |
+
num_criteria = len(config['criteria'])
|
| 325 |
+
for i in range(MAX_CRITERIA):
|
| 326 |
+
if i < num_criteria:
|
| 327 |
+
criterion = config['criteria'][i]
|
| 328 |
+
updates[sliders[i]] = gr.update(
|
| 329 |
+
label=criterion['label'], minimum=criterion['min'], maximum=criterion['max'],
|
| 330 |
+
step=criterion['step'], value=criterion['default'], visible=True
|
| 331 |
+
)
|
| 332 |
+
else:
|
| 333 |
+
updates[sliders[i]] = gr.update(visible=False, value=0)
|
| 334 |
+
return updates
|
| 335 |
+
|
| 336 |
+
def enable_submit_button():
|
| 337 |
+
return gr.update(value="Save & Next", interactive=True)
|
| 338 |
+
|
| 339 |
+
def update_instructions(config_path):
|
| 340 |
+
if not config_path: return gr.update(value="", visible=False)
|
| 341 |
+
config = load_config(config_path)
|
| 342 |
+
if config and 'instructions_markdown' in config:
|
| 343 |
+
return gr.update(value=config['instructions_markdown'], visible=True)
|
| 344 |
+
return gr.update(value="", visible=False)
|
| 345 |
+
|
| 346 |
+
def start_session(config_path):
|
| 347 |
+
if not config_path or not os.path.exists(config_path):
|
| 348 |
+
return {config_error_md: gr.update(value="**Error:** Please select a valid configuration file.", visible=True)}
|
| 349 |
+
|
| 350 |
+
config = load_config(config_path)
|
| 351 |
+
if config is None:
|
| 352 |
+
return {config_error_md: gr.update(value=f"**Error:** Could not load or parse `{config_path}`. Check console for details.", visible=True)}
|
| 353 |
+
|
| 354 |
+
samples_dir = config.get('samples_directory', 'sample-audios')
|
| 355 |
+
should_randomize = config.get('randomize_samples', False)
|
| 356 |
+
|
| 357 |
+
s_list = list_samples(samples_dir)
|
| 358 |
+
if not s_list:
|
| 359 |
+
return {config_error_md: gr.update(value=f"**Error:** No audio files found in directory: `{samples_dir}`", visible=True)}
|
| 360 |
+
|
| 361 |
+
if should_randomize: random.shuffle(s_list)
|
| 362 |
+
|
| 363 |
+
session_id = str(uuid.uuid4())
|
| 364 |
+
index = 0
|
| 365 |
+
|
| 366 |
+
updates = {
|
| 367 |
+
setup_group: gr.update(visible=False),
|
| 368 |
+
main_group: gr.update(visible=True),
|
| 369 |
+
config_error_md: gr.update(visible=False),
|
| 370 |
+
title_md: gr.update(value=f"# {config.get('title', 'Evaluation UI')}"),
|
| 371 |
+
header_md: gr.update(value=config.get('header_markdown', '')),
|
| 372 |
+
config_state: config,
|
| 373 |
+
session_id_global: session_id,
|
| 374 |
+
samples_list: s_list,
|
| 375 |
+
current_index: index,
|
| 376 |
+
}
|
| 377 |
+
sample_updates = load_sample(s_list, index, config)
|
| 378 |
+
updates.update(sample_updates)
|
| 379 |
+
return updates
|
| 380 |
+
|
| 381 |
+
def save_and_next(index, samples, annotator, sid, email, comment, config, *scores):
|
| 382 |
+
sample = samples[index]
|
| 383 |
+
samples_dir = config.get('samples_directory', 'sample-audios')
|
| 384 |
+
sample_path = os.path.join(samples_dir, sample)
|
| 385 |
+
save_status = save_response(sample, sample_path, annotator, sid, email, comment, scores, config)
|
| 386 |
+
|
| 387 |
+
next_index = index + 1
|
| 388 |
+
updates_dict = load_sample(samples, next_index, config)
|
| 389 |
+
# Provide more detailed status, including HF info if available
|
| 390 |
+
status_message = f"Saved {sample} locally."
|
| 391 |
+
if save_status.get('hf'):
|
| 392 |
+
hf_stat = save_status['hf'].get('status', 'hf_unknown')
|
| 393 |
+
status_message += f" HF status: {hf_stat}."
|
| 394 |
+
updates_dict[status] = gr.update(value=status_message)
|
| 395 |
+
|
| 396 |
+
ordered_updates = [updates_dict.get(el) for el in main_ui_elements]
|
| 397 |
+
return [next_index] + ordered_updates
|
| 398 |
+
|
| 399 |
+
# --- Event Wiring ---
|
| 400 |
+
config_dropdown.change(
|
| 401 |
+
update_instructions, inputs=[config_dropdown], outputs=[instructions_md]
|
| 402 |
+
).then(None, None, None, js="() => { document.getElementById('component-0').scrollIntoView(); }")
|
| 403 |
+
|
| 404 |
+
start_button.click(
|
| 405 |
+
start_session,
|
| 406 |
+
inputs=[config_dropdown],
|
| 407 |
+
outputs=[
|
| 408 |
+
setup_group, main_group, config_error_md, *main_ui_elements,
|
| 409 |
+
config_state, session_id_global, samples_list, current_index
|
| 410 |
+
]
|
| 411 |
+
)
|
| 412 |
+
|
| 413 |
+
submit_btn.click(
|
| 414 |
+
save_and_next,
|
| 415 |
+
inputs=[current_index, samples_list, annotator_global, session_id_global, user_email_global, comment, config_state, *sliders],
|
| 416 |
+
outputs=[current_index, *main_ui_elements]
|
| 417 |
+
)
|
| 418 |
+
|
| 419 |
+
for i, slider in enumerate(sliders):
|
| 420 |
+
slider.change(make_explainer_fn(i), inputs=[slider, config_state], outputs=[slider_explanation_md])
|
| 421 |
+
|
| 422 |
+
evaluation_audio.play(fn=enable_submit_button, inputs=None, outputs=[submit_btn])
|
| 423 |
+
|
| 424 |
+
demo.load(update_instructions, inputs=config_dropdown, outputs=instructions_md)
|
| 425 |
+
|
| 426 |
+
return demo
|
| 427 |
+
|
| 428 |
+
if __name__ == "__main__":
|
| 429 |
+
app = make_ui()
|
| 430 |
+
app.launch(server_name="0.0.0.0", server_port=7860)
|
config_mos.yaml
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Configuration for a standard Mean Opinion Score (MOS) test.
|
| 2 |
+
title: "MOS Test - Audio Quality Evaluation"
|
| 3 |
+
header_markdown: "Listen to the audio sample and rate its overall quality on a scale of 1 to 5."
|
| 4 |
+
|
| 5 |
+
instructions_markdown: |
|
| 6 |
+
**Welcome, Annotator!**
|
| 7 |
+
|
| 8 |
+
Instructions for MOS test:
|
| 9 |
+
|
| 10 |
+
Please follow these steps carefully:
|
| 11 |
+
1. Enter your unique **Annotator ID** before you begin.
|
| 12 |
+
2. Listen to each audio clip from start to finish.
|
| 13 |
+
3. Rate the clip using the sliders provided based on the scoring guide.
|
| 14 |
+
4. Provide any extra details in the comments box.
|
| 15 |
+
5. Click 'Save & Next' to submit your rating and load the next clip.
|
| 16 |
+
|
| 17 |
+
# The directory where your audio files are stored.
|
| 18 |
+
samples_directory: "sample-audios"
|
| 19 |
+
|
| 20 |
+
# Set to 'true' to shuffle the audio files, 'false' for alphabetical order.
|
| 21 |
+
randomize_samples: true
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
# MOS tests typically use a single criterion for overall quality.
|
| 25 |
+
criteria:
|
| 26 |
+
- label: "Overall Quality"
|
| 27 |
+
min: 1
|
| 28 |
+
max: 5
|
| 29 |
+
step: 1
|
| 30 |
+
default: 3
|
| 31 |
+
# These are standard definitions for the 5-point Absolute Category Rating (ACR) scale.
|
| 32 |
+
explanations:
|
| 33 |
+
1: "Bad - The quality is very distracting and unpleasant."
|
| 34 |
+
2: "Poor - The quality is distracting and annoying."
|
| 35 |
+
3: "Fair - The quality is slightly distracting, but acceptable."
|
| 36 |
+
4: "Good - The quality is not distracting, it is fine."
|
| 37 |
+
5: "Excellent - The quality is flawless and natural."
|
config_original.yaml
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# General UI Configuration
|
| 2 |
+
title: "TTS Rubric — Dynamic Evaluation"
|
| 3 |
+
|
| 4 |
+
instructions_markdown: |
|
| 5 |
+
**Welcome annotator!**
|
| 6 |
+
|
| 7 |
+
Instructions for multiple aspect test
|
| 8 |
+
|
| 9 |
+
Please follow these steps carefully:
|
| 10 |
+
1. Enter your unique **Annotator ID** before you begin.
|
| 11 |
+
2. Listen to each audio clip from start to finish.
|
| 12 |
+
3. Rate the clip using the sliders provided based on the scoring guide.
|
| 13 |
+
4. Provide any extra details in the comments box.
|
| 14 |
+
5. Click 'Save & Next' to submit your rating and load the next clip.
|
| 15 |
+
|
| 16 |
+
# The directory where your audio files are stored.
|
| 17 |
+
samples_directory: "sample-audios"
|
| 18 |
+
|
| 19 |
+
# Set to 'true' to shuffle the audio files, 'false' for alphabetical order.
|
| 20 |
+
randomize_samples: true
|
| 21 |
+
# Define the evaluation criteria. The UI will be built from this list.
|
| 22 |
+
criteria:
|
| 23 |
+
- label: "Clarity & Intelligibility"
|
| 24 |
+
min: 1
|
| 25 |
+
max: 5
|
| 26 |
+
step: 1
|
| 27 |
+
default: 3
|
| 28 |
+
explanations:
|
| 29 |
+
1: "Unacceptable."
|
| 30 |
+
2: "Often unclear or distorted; difficult to follow."
|
| 31 |
+
3: "Understandable but requires effort; some words unclear."
|
| 32 |
+
4: "Mostly clear, minor issues (with fast/slow playback)."
|
| 33 |
+
5: "Speech is clear, easy to understand (at all speeds)."
|
| 34 |
+
|
| 35 |
+
- label: "Accent & Pronunciation"
|
| 36 |
+
min: 1
|
| 37 |
+
max: 5
|
| 38 |
+
step: 1
|
| 39 |
+
default: 3
|
| 40 |
+
explanations:
|
| 41 |
+
1: "Severe pronunciation problems; largely unintelligible."
|
| 42 |
+
2: "Frequent pronunciation issues that impede understanding."
|
| 43 |
+
3: "Some mispronunciations that require effort to interpret."
|
| 44 |
+
4: "Minor pronunciation quirks but overall fine."
|
| 45 |
+
5: "Pronunciation is natural and appropriate for the target dialect."
|
| 46 |
+
|
| 47 |
+
- label: "Tone & Suitability"
|
| 48 |
+
min: 1
|
| 49 |
+
max: 5
|
| 50 |
+
step: 1
|
| 51 |
+
default: 3
|
| 52 |
+
explanations:
|
| 53 |
+
1: "Tone is inappropriate or harmful for the content."
|
| 54 |
+
2: "Tone often feels off or distracting from the content."
|
| 55 |
+
3: "Tone is acceptable but occasionally inappropriate."
|
| 56 |
+
4: "Generally appropriate tone with small mismatches."
|
| 57 |
+
5: "Tone fits the content and use-case perfectly."
|
| 58 |
+
|
| 59 |
+
- label: "Voice quality"
|
| 60 |
+
min: 1
|
| 61 |
+
max: 5
|
| 62 |
+
step: 1
|
| 63 |
+
default: 3
|
| 64 |
+
explanations:
|
| 65 |
+
1: "Unusable voice quality."
|
| 66 |
+
2: "Poor quality with frequent artifacts."
|
| 67 |
+
3: "Noticeable quality issues but still usable."
|
| 68 |
+
4: "Minor artifacts but overall high quality."
|
| 69 |
+
5: "Natural, pleasant voice with no artifacts."
|
| 70 |
+
|
| 71 |
+
- label: "Customization & Flexibility"
|
| 72 |
+
min: 1
|
| 73 |
+
max: 5
|
| 74 |
+
step: 1
|
| 75 |
+
default: 3
|
| 76 |
+
explanations:
|
| 77 |
+
1: "No useful customization; inflexible."
|
| 78 |
+
2: "Very limited or brittle customization options."
|
| 79 |
+
3: "Limited customization; acceptable for simple use-cases."
|
| 80 |
+
4: "Some customization available; works well for most cases."
|
| 81 |
+
5: "Highly flexible and customizable for different styles."
|
| 82 |
+
|
| 83 |
+
- label: "Listening comfort"
|
| 84 |
+
min: 1
|
| 85 |
+
max: 5
|
| 86 |
+
step: 1
|
| 87 |
+
default: 3
|
| 88 |
+
explanations:
|
| 89 |
+
1: "Uncomfortable or painful to listen to."
|
| 90 |
+
2: "Often fatiguing or distracting to listen to."
|
| 91 |
+
3: "Some listening fatigue; tolerable for short durations."
|
| 92 |
+
4: "Mostly comfortable with occasional sharpness or fatigue."
|
| 93 |
+
5: "Comfortable to listen to for extended periods."
|
download_dataset.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Download and merge all data files from a Hugging Face dataset repo.
|
| 2 |
+
|
| 3 |
+
Usage:
|
| 4 |
+
HF_TOKEN must be exported in your environment (or pass --token).
|
| 5 |
+
HF_DATASET_ID may be exported or passed via --repo.
|
| 6 |
+
|
| 7 |
+
Example:
|
| 8 |
+
export HF_TOKEN="hf_..."
|
| 9 |
+
python download_dataset.py --repo kathiasi/tts-rubric-responses --outdir out
|
| 10 |
+
|
| 11 |
+
This script downloads any files under `data/` (parquet or arrow/ipc), reads them,
|
| 12 |
+
concatenates into a single table, and writes `combined.parquet` and `combined.csv` in
|
| 13 |
+
`outdir`.
|
| 14 |
+
"""
|
| 15 |
+
import os
|
| 16 |
+
import argparse
|
| 17 |
+
import json
|
| 18 |
+
from huggingface_hub import HfApi, hf_hub_download
|
| 19 |
+
import pyarrow.parquet as pq
|
| 20 |
+
import pyarrow.ipc as ipc
|
| 21 |
+
import pandas as pd
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def read_parquet(path):
|
| 25 |
+
try:
|
| 26 |
+
tbl = pq.read_table(path)
|
| 27 |
+
return tbl.to_pandas()
|
| 28 |
+
except Exception as e:
|
| 29 |
+
raise RuntimeError(f"Failed to read parquet {path}: {e}")
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def read_arrow(path):
|
| 33 |
+
try:
|
| 34 |
+
with open(path, 'rb') as f:
|
| 35 |
+
reader = ipc.open_file(f)
|
| 36 |
+
tbl = reader.read_all()
|
| 37 |
+
return tbl.to_pandas()
|
| 38 |
+
except Exception as e:
|
| 39 |
+
raise RuntimeError(f"Failed to read arrow/ipc {path}: {e}")
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def download_and_merge(repo_id, outdir, token=None):
|
| 43 |
+
api = HfApi()
|
| 44 |
+
token = token or os.environ.get('HF_TOKEN')
|
| 45 |
+
if not token:
|
| 46 |
+
raise RuntimeError('HF_TOKEN not provided; export HF_TOKEN or pass --token')
|
| 47 |
+
|
| 48 |
+
files = api.list_repo_files(repo_id=repo_id, repo_type='dataset', token=token)
|
| 49 |
+
data_files = [f for f in files if f.startswith('data/')]
|
| 50 |
+
if not data_files:
|
| 51 |
+
print('No data/ files found in dataset repo. Files found:')
|
| 52 |
+
print(json.dumps(files, indent=2))
|
| 53 |
+
return
|
| 54 |
+
|
| 55 |
+
os.makedirs(outdir, exist_ok=True)
|
| 56 |
+
dfs = []
|
| 57 |
+
for fname in sorted(data_files):
|
| 58 |
+
print('Processing', fname)
|
| 59 |
+
local_path = hf_hub_download(repo_id=repo_id, repo_type='dataset', filename=fname, token=token)
|
| 60 |
+
if fname.endswith('.parquet'):
|
| 61 |
+
df = read_parquet(local_path)
|
| 62 |
+
elif fname.endswith('.arrow') or fname.endswith('.ipc'):
|
| 63 |
+
df = read_arrow(local_path)
|
| 64 |
+
else:
|
| 65 |
+
print('Skipping unsupported data file:', fname)
|
| 66 |
+
continue
|
| 67 |
+
dfs.append(df)
|
| 68 |
+
|
| 69 |
+
if not dfs:
|
| 70 |
+
print('No supported data files were read.')
|
| 71 |
+
return
|
| 72 |
+
|
| 73 |
+
combined = pd.concat(dfs, ignore_index=True)
|
| 74 |
+
out_parquet = os.path.join(outdir, 'combined.parquet')
|
| 75 |
+
out_csv = os.path.join(outdir, 'combined.csv')
|
| 76 |
+
print(f'Writing {len(combined)} rows to', out_parquet)
|
| 77 |
+
combined.to_parquet(out_parquet, index=False)
|
| 78 |
+
print('Also writing CSV to', out_csv)
|
| 79 |
+
combined.to_csv(out_csv, index=False)
|
| 80 |
+
print('Done.')
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
if __name__ == '__main__':
|
| 84 |
+
p = argparse.ArgumentParser()
|
| 85 |
+
p.add_argument('--repo', help='Dataset repo id (user/name)', default=os.environ.get('HF_DATASET_ID'))
|
| 86 |
+
p.add_argument('--outdir', help='Output directory', default='hf_dataset')
|
| 87 |
+
p.add_argument('--token', help='Hugging Face token (optional)', default=None)
|
| 88 |
+
args = p.parse_args()
|
| 89 |
+
|
| 90 |
+
if not args.repo:
|
| 91 |
+
print('Dataset repo id is required via --repo or HF_DATASET_ID env var')
|
| 92 |
+
raise SystemExit(1)
|
| 93 |
+
|
| 94 |
+
download_and_merge(args.repo, args.outdir, token=args.token)
|
requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio==5.15
|
| 2 |
+
numpy
|
| 3 |
+
python-docx
|
| 4 |
+
PyYAML
|
| 5 |
+
huggingface_hub>=0.28.1
|
| 6 |
+
pandas
|
| 7 |
+
|
| 8 |
+
|
sample-audios/sorsamisk_-_115_01_-_Goevten_voestes_biejjie_015_024_s.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d08e73cfdf0d3c6aaf1eef5b4a028b96f6dac88a0f673ed654faabdabb0b82cd
|
| 3 |
+
size 827436
|
sample-audios/sorsamisk_-_7_01_-_Jarkoestidh_028_020.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3292ee4d5f741001bff3a8ac7e2c5d18daee9b227f7a36cf43632205b6983b91
|
| 3 |
+
size 209532
|
sample-audios/sorsamisk_-_III_01_-_Giesie_eejehtimmiebiejjieh_015_015.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:98c6fc87df1f69d24878c4f04fce7f7713c574a1bd531001a65f66b096e0ad40
|
| 3 |
+
size 295016
|
sample-audios/sorsamisk_goltelidh_jupmelen_rihjke_lea_gietskesne_cd1_mono_022_009.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f3bbcaea90f2bdb4bf798c8e4214ddf2381eedaf3ffd974509ca54d02dc5504b
|
| 3 |
+
size 468702
|
sample-audios/sorsamisk_goltelidh_jupmelen_rihjke_lea_gietskesne_cd1_mono_030_014.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:90bc9504d119bc8a95ab02c16299c054725c7b32fa55e9b5b2dbe847fb297d83
|
| 3 |
+
size 458130
|
sample-audios/sorsamisk_goltelidh_jupmelen_rihjke_lea_gietskesne_cd2_009_TRN_019.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:71ccd5595e3ed565eba27cd1ce48a07cd954717c196a9ea3a672bbe9cf043290
|
| 3 |
+
size 147504
|
sample-audios/sorsamisk_goltelidh_jupmelen_rihjke_lea_gietskesne_cd2_009_TRN_019_s.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:86554d671d506fef1863a3b026ccbfb77e6fd6e2112b4380b9ccab18acd3f9a1
|
| 3 |
+
size 143916
|