Upload app.py with huggingface_hub
Browse files
app.py
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Gradio Hugging Face Space: wake-word dataset creator.
|
| 2 |
+
|
| 3 |
+
Generates a keyword-spotting dataset using Google Cloud TTS when an API
|
| 4 |
+
key is supplied, and automatically falls back to free Piper TTS otherwise.
|
| 5 |
+
Optionally pushes the result to a Hugging Face dataset repo and/or uploads
|
| 6 |
+
directly to an Edge Impulse project.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
import os
|
| 12 |
+
import shutil
|
| 13 |
+
import tempfile
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
from typing import List, Optional
|
| 16 |
+
|
| 17 |
+
import gradio as gr
|
| 18 |
+
|
| 19 |
+
from src import edge_impulse
|
| 20 |
+
from src.backends import select_backend
|
| 21 |
+
from src.builder import build_dataset
|
| 22 |
+
from src.config import (
|
| 23 |
+
DEFAULT_UNKNOWN_PHRASES,
|
| 24 |
+
DEFAULT_WAKE_PHRASES,
|
| 25 |
+
DatasetConfig,
|
| 26 |
+
)
|
| 27 |
+
from src.hf_export import export_hf_dataset, push_to_hub
|
| 28 |
+
|
| 29 |
+
# API keys can also be provided as Space secrets.
|
| 30 |
+
ENV_GCP_KEY = os.environ.get("GCP_TTS_API_KEY", "")
|
| 31 |
+
ENV_HF_TOKEN = os.environ.get("HF_TOKEN", "")
|
| 32 |
+
ENV_EI_KEY = os.environ.get("EDGE_IMPULSE_API_KEY", "")
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def _split_lines(text: str, fallback: List[str]) -> List[str]:
|
| 36 |
+
items = [line.strip() for line in (text or "").splitlines() if line.strip()]
|
| 37 |
+
return items or list(fallback)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def create_dataset(
|
| 41 |
+
dataset_name: str,
|
| 42 |
+
wake_label: str,
|
| 43 |
+
wake_phrases_text: str,
|
| 44 |
+
unknown_phrases_text: str,
|
| 45 |
+
gcp_api_key: str,
|
| 46 |
+
base_repeats: int,
|
| 47 |
+
augmentations: int,
|
| 48 |
+
background_noise: int,
|
| 49 |
+
max_voices: int,
|
| 50 |
+
test_ratio: float,
|
| 51 |
+
hf_repo_id: str,
|
| 52 |
+
hf_token: str,
|
| 53 |
+
hf_private: bool,
|
| 54 |
+
do_push_hf: bool,
|
| 55 |
+
ei_api_key: str,
|
| 56 |
+
do_upload_ei: bool,
|
| 57 |
+
ei_allow_duplicates: bool,
|
| 58 |
+
progress=gr.Progress(track_tqdm=False),
|
| 59 |
+
):
|
| 60 |
+
logs: List[str] = []
|
| 61 |
+
|
| 62 |
+
def log(message: str) -> str:
|
| 63 |
+
logs.append(message)
|
| 64 |
+
return "\n".join(logs)
|
| 65 |
+
|
| 66 |
+
work_root = Path(tempfile.mkdtemp(prefix="wakeword_"))
|
| 67 |
+
dataset_dir = work_root / "dataset"
|
| 68 |
+
hf_dir = work_root / "hf_dataset"
|
| 69 |
+
|
| 70 |
+
gcp_api_key = (gcp_api_key or "").strip() or ENV_GCP_KEY
|
| 71 |
+
|
| 72 |
+
try:
|
| 73 |
+
progress(0.05, desc="Selecting TTS backend")
|
| 74 |
+
log("Selecting TTS backend...")
|
| 75 |
+
backend = select_backend(
|
| 76 |
+
gcp_api_key=gcp_api_key,
|
| 77 |
+
language_prefixes=["en", "nl", "de", "fr", "es"],
|
| 78 |
+
max_gcp_voices_per_locale=3,
|
| 79 |
+
max_piper_voices=int(max_voices),
|
| 80 |
+
sample_rate_hz=16000,
|
| 81 |
+
)
|
| 82 |
+
engine = (
|
| 83 |
+
"Google Cloud TTS"
|
| 84 |
+
if backend.source == "google_cloud_tts"
|
| 85 |
+
else "Piper TTS (free fallback)"
|
| 86 |
+
)
|
| 87 |
+
yield log(f"Using backend: {engine}"), None, None
|
| 88 |
+
|
| 89 |
+
config = DatasetConfig(
|
| 90 |
+
out_dir=str(dataset_dir),
|
| 91 |
+
dataset_name=dataset_name or "hey_android",
|
| 92 |
+
wake_label=wake_label or "hey_android",
|
| 93 |
+
wake_phrases=_split_lines(wake_phrases_text, DEFAULT_WAKE_PHRASES),
|
| 94 |
+
unknown_phrases=_split_lines(unknown_phrases_text, DEFAULT_UNKNOWN_PHRASES),
|
| 95 |
+
base_repeats_per_phrase_per_voice=int(base_repeats),
|
| 96 |
+
augmentations_per_speech_clip=int(augmentations),
|
| 97 |
+
background_noise_samples=int(background_noise),
|
| 98 |
+
max_piper_voices=int(max_voices),
|
| 99 |
+
test_ratio=float(test_ratio),
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
progress(0.15, desc="Generating audio")
|
| 103 |
+
result = build_dataset(config, backend, progress=lambda m: logs.append(m))
|
| 104 |
+
yield log(
|
| 105 |
+
f"Generated {result.total_samples} samples "
|
| 106 |
+
f"(base={result.generated_base}, augmented={result.generated_augmented}, "
|
| 107 |
+
f"failed={result.failed})."
|
| 108 |
+
), None, None
|
| 109 |
+
|
| 110 |
+
progress(0.7, desc="Preparing Hugging Face layout")
|
| 111 |
+
export_hf_dataset(config, result, str(hf_dir), repo_id=hf_repo_id or "your-username/your-dataset")
|
| 112 |
+
log("Hugging Face dataset folder prepared.")
|
| 113 |
+
|
| 114 |
+
# Zip for download.
|
| 115 |
+
zip_base = work_root / f"{config.dataset_name}_hf_dataset"
|
| 116 |
+
zip_path = shutil.make_archive(str(zip_base), "zip", str(hf_dir))
|
| 117 |
+
yield log(f"Created download archive: {Path(zip_path).name}"), zip_path, None
|
| 118 |
+
|
| 119 |
+
# Optional: push to Hugging Face Hub.
|
| 120 |
+
token = (hf_token or "").strip() or ENV_HF_TOKEN
|
| 121 |
+
if do_push_hf:
|
| 122 |
+
if not token:
|
| 123 |
+
log("Skipping HF push: no token provided.")
|
| 124 |
+
elif not hf_repo_id or "/" not in hf_repo_id:
|
| 125 |
+
log("Skipping HF push: provide a repo id like 'username/dataset-name'.")
|
| 126 |
+
else:
|
| 127 |
+
progress(0.85, desc="Pushing to Hugging Face")
|
| 128 |
+
log(f"Pushing to Hugging Face dataset '{hf_repo_id}'...")
|
| 129 |
+
url = push_to_hub(str(hf_dir), hf_repo_id, token, private=bool(hf_private))
|
| 130 |
+
log(f"Pushed: {url}")
|
| 131 |
+
yield "\n".join(logs), zip_path, None
|
| 132 |
+
|
| 133 |
+
# Optional: upload to Edge Impulse.
|
| 134 |
+
ei_key = (ei_api_key or "").strip() or ENV_EI_KEY
|
| 135 |
+
if do_upload_ei:
|
| 136 |
+
if not ei_key:
|
| 137 |
+
log("Skipping Edge Impulse upload: no API key provided.")
|
| 138 |
+
else:
|
| 139 |
+
progress(0.92, desc="Uploading to Edge Impulse")
|
| 140 |
+
log("Uploading dataset to your Edge Impulse project...")
|
| 141 |
+
ei_result = edge_impulse.upload_dataset(
|
| 142 |
+
dataset_dir=str(dataset_dir),
|
| 143 |
+
api_key=ei_key,
|
| 144 |
+
allow_duplicates=bool(ei_allow_duplicates),
|
| 145 |
+
progress=lambda m: logs.append(m),
|
| 146 |
+
)
|
| 147 |
+
log(
|
| 148 |
+
f"Edge Impulse: {ei_result.uploaded} uploaded, {ei_result.failed} failed."
|
| 149 |
+
)
|
| 150 |
+
if ei_result.errors:
|
| 151 |
+
log("Edge Impulse errors:\n" + "\n".join(ei_result.errors[:5]))
|
| 152 |
+
|
| 153 |
+
progress(1.0, desc="Done")
|
| 154 |
+
summary = (
|
| 155 |
+
f"### Done\n"
|
| 156 |
+
f"- Backend: **{engine}**\n"
|
| 157 |
+
f"- Total samples: **{result.total_samples}**\n"
|
| 158 |
+
+ "\n".join(f"- `{k}`: {v}" for k, v in sorted(result.label_counts.items()))
|
| 159 |
+
)
|
| 160 |
+
yield "\n".join(logs), zip_path, summary
|
| 161 |
+
|
| 162 |
+
except Exception as exc: # noqa: BLE001 - surface errors to the UI
|
| 163 |
+
log(f"ERROR: {exc}")
|
| 164 |
+
yield "\n".join(logs), None, f"### Failed\n\n```\n{exc}\n```"
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
with gr.Blocks(title="WakeForge — GCP & Piper TTS Wake Word Dataset Creator") as demo:
|
| 168 |
+
gr.Markdown(
|
| 169 |
+
"""
|
| 170 |
+
# 🔨 WakeForge
|
| 171 |
+
### GCP & Piper TTS Wake Word Dataset Creator
|
| 172 |
+
Generate a keyword-spotting dataset for **Hugging Face** and **Edge Impulse**.
|
| 173 |
+
|
| 174 |
+
- Provide a **Google Cloud TTS API key** to use Google voices.
|
| 175 |
+
- **No key? It automatically falls back to free Piper TTS.**
|
| 176 |
+
- Optionally **push to a Hugging Face dataset** and/or **upload to your Edge Impulse project**.
|
| 177 |
+
"""
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
with gr.Row():
|
| 181 |
+
with gr.Column():
|
| 182 |
+
gr.Markdown("### Dataset")
|
| 183 |
+
dataset_name = gr.Textbox(label="Dataset name", value="hey_android")
|
| 184 |
+
wake_label = gr.Textbox(label="Wake label", value="hey_android")
|
| 185 |
+
wake_phrases_text = gr.Textbox(
|
| 186 |
+
label="Wake phrases (one per line)",
|
| 187 |
+
value="\n".join(DEFAULT_WAKE_PHRASES),
|
| 188 |
+
lines=6,
|
| 189 |
+
)
|
| 190 |
+
unknown_phrases_text = gr.Textbox(
|
| 191 |
+
label="Unknown / near-miss phrases (one per line)",
|
| 192 |
+
value="\n".join(DEFAULT_UNKNOWN_PHRASES),
|
| 193 |
+
lines=8,
|
| 194 |
+
)
|
| 195 |
+
|
| 196 |
+
gr.Markdown("### Size")
|
| 197 |
+
base_repeats = gr.Slider(1, 5, value=1, step=1, label="Base clips per phrase per voice")
|
| 198 |
+
augmentations = gr.Slider(0, 20, value=8, step=1, label="Augmentations per clip")
|
| 199 |
+
background_noise = gr.Slider(0, 500, value=200, step=10, label="Background noise clips")
|
| 200 |
+
max_voices = gr.Slider(1, 7, value=7, step=1, label="Max voices")
|
| 201 |
+
test_ratio = gr.Slider(0.05, 0.5, value=0.2, step=0.05, label="Test split ratio")
|
| 202 |
+
|
| 203 |
+
with gr.Column():
|
| 204 |
+
gr.Markdown("### Google Cloud TTS (optional)")
|
| 205 |
+
gcp_api_key = gr.Textbox(
|
| 206 |
+
label="GCP TTS API key",
|
| 207 |
+
type="password",
|
| 208 |
+
placeholder="Leave blank to use free Piper TTS",
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
gr.Markdown("### Push to Hugging Face (optional)")
|
| 212 |
+
do_push_hf = gr.Checkbox(label="Push dataset to Hugging Face Hub", value=False)
|
| 213 |
+
hf_repo_id = gr.Textbox(label="HF dataset repo id", placeholder="username/dataset-name")
|
| 214 |
+
hf_token = gr.Textbox(label="HF write token", type="password", placeholder="hf_...")
|
| 215 |
+
hf_private = gr.Checkbox(label="Private dataset", value=False)
|
| 216 |
+
|
| 217 |
+
gr.Markdown("### Upload to Edge Impulse (optional)")
|
| 218 |
+
do_upload_ei = gr.Checkbox(label="Upload dataset to Edge Impulse project", value=False)
|
| 219 |
+
ei_api_key = gr.Textbox(
|
| 220 |
+
label="Edge Impulse API key",
|
| 221 |
+
type="password",
|
| 222 |
+
placeholder="ei_... (Project → Dashboard → Keys)",
|
| 223 |
+
)
|
| 224 |
+
ei_allow_duplicates = gr.Checkbox(label="Allow duplicate samples", value=False)
|
| 225 |
+
|
| 226 |
+
generate_btn = gr.Button("Generate dataset", variant="primary")
|
| 227 |
+
|
| 228 |
+
summary_md = gr.Markdown()
|
| 229 |
+
download = gr.File(label="Download dataset (zip)")
|
| 230 |
+
logs_box = gr.Textbox(label="Logs", lines=16, max_lines=30)
|
| 231 |
+
|
| 232 |
+
generate_btn.click(
|
| 233 |
+
fn=create_dataset,
|
| 234 |
+
inputs=[
|
| 235 |
+
dataset_name, wake_label, wake_phrases_text, unknown_phrases_text,
|
| 236 |
+
gcp_api_key, base_repeats, augmentations, background_noise, max_voices, test_ratio,
|
| 237 |
+
hf_repo_id, hf_token, hf_private, do_push_hf,
|
| 238 |
+
ei_api_key, do_upload_ei, ei_allow_duplicates,
|
| 239 |
+
],
|
| 240 |
+
outputs=[logs_box, download, summary_md],
|
| 241 |
+
)
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
if __name__ == "__main__":
|
| 245 |
+
demo.queue().launch()
|