| """Gradio Hugging Face Space: wake-word dataset creator. |
| |
| Generates a keyword-spotting dataset using Google Cloud TTS when an API |
| key is supplied, and automatically falls back to free Piper TTS otherwise. |
| Optionally pushes the result to a Hugging Face dataset repo and/or uploads |
| directly to an Edge Impulse project. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import os |
| import shutil |
| import tempfile |
| from pathlib import Path |
| from typing import List, Optional |
|
|
| import gradio as gr |
|
|
| from src import edge_impulse |
| from src.backends import select_backend |
| from src.builder import build_dataset |
| from src.config import ( |
| DEFAULT_UNKNOWN_PHRASES, |
| DEFAULT_WAKE_PHRASES, |
| DatasetConfig, |
| ) |
| from src.hf_export import export_hf_dataset, push_to_hub |
|
|
| |
| ENV_GCP_KEY = os.environ.get("GCP_TTS_API_KEY", "") |
| ENV_HF_TOKEN = os.environ.get("HF_TOKEN", "") |
| ENV_EI_KEY = os.environ.get("EDGE_IMPULSE_API_KEY", "") |
|
|
|
|
| def _split_lines(text: str, fallback: List[str]) -> List[str]: |
| items = [line.strip() for line in (text or "").splitlines() if line.strip()] |
| return items or list(fallback) |
|
|
|
|
| def create_dataset( |
| dataset_name: str, |
| wake_label: str, |
| wake_phrases_text: str, |
| unknown_phrases_text: str, |
| gcp_api_key: str, |
| base_repeats: int, |
| augmentations: int, |
| background_noise: int, |
| max_voices: int, |
| test_ratio: float, |
| hf_repo_id: str, |
| hf_token: str, |
| hf_private: bool, |
| do_push_hf: bool, |
| ei_api_key: str, |
| do_upload_ei: bool, |
| ei_allow_duplicates: bool, |
| progress=gr.Progress(track_tqdm=False), |
| ): |
| logs: List[str] = [] |
|
|
| def log(message: str) -> str: |
| logs.append(message) |
| return "\n".join(logs) |
|
|
| work_root = Path(tempfile.mkdtemp(prefix="wakeword_")) |
| dataset_dir = work_root / "dataset" |
| hf_dir = work_root / "hf_dataset" |
|
|
| gcp_api_key = (gcp_api_key or "").strip() or ENV_GCP_KEY |
|
|
| try: |
| progress(0.05, desc="Selecting TTS backend") |
| log("Selecting TTS backend...") |
| backend = select_backend( |
| gcp_api_key=gcp_api_key, |
| language_prefixes=["en", "nl", "de", "fr", "es"], |
| max_gcp_voices_per_locale=3, |
| max_piper_voices=int(max_voices), |
| sample_rate_hz=16000, |
| ) |
| engine = ( |
| "Google Cloud TTS" |
| if backend.source == "google_cloud_tts" |
| else "Piper TTS (free fallback)" |
| ) |
| yield log(f"Using backend: {engine}"), None, None |
|
|
| config = DatasetConfig( |
| out_dir=str(dataset_dir), |
| dataset_name=dataset_name or "hey_android", |
| wake_label=wake_label or "hey_android", |
| wake_phrases=_split_lines(wake_phrases_text, DEFAULT_WAKE_PHRASES), |
| unknown_phrases=_split_lines(unknown_phrases_text, DEFAULT_UNKNOWN_PHRASES), |
| base_repeats_per_phrase_per_voice=int(base_repeats), |
| augmentations_per_speech_clip=int(augmentations), |
| background_noise_samples=int(background_noise), |
| max_piper_voices=int(max_voices), |
| test_ratio=float(test_ratio), |
| ) |
|
|
| progress(0.15, desc="Generating audio") |
| result = build_dataset(config, backend, progress=lambda m: logs.append(m)) |
| yield log( |
| f"Generated {result.total_samples} samples " |
| f"(base={result.generated_base}, augmented={result.generated_augmented}, " |
| f"failed={result.failed})." |
| ), None, None |
|
|
| progress(0.7, desc="Preparing Hugging Face layout") |
| export_hf_dataset(config, result, str(hf_dir), repo_id=hf_repo_id or "your-username/your-dataset") |
| log("Hugging Face dataset folder prepared.") |
|
|
| |
| zip_base = work_root / f"{config.dataset_name}_hf_dataset" |
| zip_path = shutil.make_archive(str(zip_base), "zip", str(hf_dir)) |
| yield log(f"Created download archive: {Path(zip_path).name}"), zip_path, None |
|
|
| |
| token = (hf_token or "").strip() or ENV_HF_TOKEN |
| if do_push_hf: |
| if not token: |
| log("Skipping HF push: no token provided.") |
| elif not hf_repo_id or "/" not in hf_repo_id: |
| log("Skipping HF push: provide a repo id like 'username/dataset-name'.") |
| else: |
| progress(0.85, desc="Pushing to Hugging Face") |
| log(f"Pushing to Hugging Face dataset '{hf_repo_id}'...") |
| url = push_to_hub(str(hf_dir), hf_repo_id, token, private=bool(hf_private)) |
| log(f"Pushed: {url}") |
| yield "\n".join(logs), zip_path, None |
|
|
| |
| ei_key = (ei_api_key or "").strip() or ENV_EI_KEY |
| if do_upload_ei: |
| if not ei_key: |
| log("Skipping Edge Impulse upload: no API key provided.") |
| else: |
| progress(0.92, desc="Uploading to Edge Impulse") |
| log("Uploading dataset to your Edge Impulse project...") |
| ei_result = edge_impulse.upload_dataset( |
| dataset_dir=str(dataset_dir), |
| api_key=ei_key, |
| allow_duplicates=bool(ei_allow_duplicates), |
| progress=lambda m: logs.append(m), |
| ) |
| log( |
| f"Edge Impulse: {ei_result.uploaded} uploaded, {ei_result.failed} failed." |
| ) |
| if ei_result.errors: |
| log("Edge Impulse errors:\n" + "\n".join(ei_result.errors[:5])) |
|
|
| progress(1.0, desc="Done") |
| summary = ( |
| f"### Done\n" |
| f"- Backend: **{engine}**\n" |
| f"- Total samples: **{result.total_samples}**\n" |
| + "\n".join(f"- `{k}`: {v}" for k, v in sorted(result.label_counts.items())) |
| ) |
| yield "\n".join(logs), zip_path, summary |
|
|
| except Exception as exc: |
| log(f"ERROR: {exc}") |
| yield "\n".join(logs), None, f"### Failed\n\n```\n{exc}\n```" |
|
|
|
|
| with gr.Blocks(title="WakeForge — GCP & Piper TTS Wake Word Dataset Creator") as demo: |
| gr.Markdown( |
| """ |
| # 🔨 WakeForge |
| ### GCP & Piper TTS Wake Word Dataset Creator |
| Generate a keyword-spotting dataset for **Hugging Face** and **Edge Impulse**. |
| |
| - Provide a **Google Cloud TTS API key** to use Google voices. |
| - **No key? It automatically falls back to free Piper TTS.** |
| - Optionally **push to a Hugging Face dataset** and/or **upload to your Edge Impulse project**. |
| """ |
| ) |
|
|
| with gr.Row(): |
| with gr.Column(): |
| gr.Markdown("### Dataset") |
| dataset_name = gr.Textbox(label="Dataset name", value="hey_android") |
| wake_label = gr.Textbox(label="Wake label", value="hey_android") |
| wake_phrases_text = gr.Textbox( |
| label="Wake phrases (one per line)", |
| value="\n".join(DEFAULT_WAKE_PHRASES), |
| lines=6, |
| ) |
| unknown_phrases_text = gr.Textbox( |
| label="Unknown / near-miss phrases (one per line)", |
| value="\n".join(DEFAULT_UNKNOWN_PHRASES), |
| lines=8, |
| ) |
|
|
| gr.Markdown("### Size") |
| base_repeats = gr.Slider(1, 5, value=1, step=1, label="Base clips per phrase per voice") |
| augmentations = gr.Slider(0, 20, value=8, step=1, label="Augmentations per clip") |
| background_noise = gr.Slider(0, 500, value=200, step=10, label="Background noise clips") |
| max_voices = gr.Slider(1, 7, value=7, step=1, label="Max voices") |
| test_ratio = gr.Slider(0.05, 0.5, value=0.2, step=0.05, label="Test split ratio") |
|
|
| with gr.Column(): |
| gr.Markdown("### Google Cloud TTS (optional)") |
| gcp_api_key = gr.Textbox( |
| label="GCP TTS API key", |
| type="password", |
| placeholder="Leave blank to use free Piper TTS", |
| ) |
|
|
| gr.Markdown("### Push to Hugging Face (optional)") |
| do_push_hf = gr.Checkbox(label="Push dataset to Hugging Face Hub", value=False) |
| hf_repo_id = gr.Textbox(label="HF dataset repo id", placeholder="username/dataset-name") |
| hf_token = gr.Textbox(label="HF write token", type="password", placeholder="hf_...") |
| hf_private = gr.Checkbox(label="Private dataset", value=False) |
|
|
| gr.Markdown("### Upload to Edge Impulse (optional)") |
| do_upload_ei = gr.Checkbox(label="Upload dataset to Edge Impulse project", value=False) |
| ei_api_key = gr.Textbox( |
| label="Edge Impulse API key", |
| type="password", |
| placeholder="ei_... (Project → Dashboard → Keys)", |
| ) |
| ei_allow_duplicates = gr.Checkbox(label="Allow duplicate samples", value=False) |
|
|
| generate_btn = gr.Button("Generate dataset", variant="primary") |
|
|
| summary_md = gr.Markdown() |
| download = gr.File(label="Download dataset (zip)") |
| logs_box = gr.Textbox(label="Logs", lines=16, max_lines=30) |
|
|
| generate_btn.click( |
| fn=create_dataset, |
| inputs=[ |
| dataset_name, wake_label, wake_phrases_text, unknown_phrases_text, |
| gcp_api_key, base_repeats, augmentations, background_noise, max_voices, test_ratio, |
| hf_repo_id, hf_token, hf_private, do_push_hf, |
| ei_api_key, do_upload_ei, ei_allow_duplicates, |
| ], |
| outputs=[logs_box, download, summary_md], |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| demo.queue().launch() |
|
|