wakeforge / app.py
eoinedge's picture
Upload app.py with huggingface_hub
93042b3 verified
Raw
History Blame Contribute Delete
9.66 kB
"""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
# API keys can also be provided as Space secrets.
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 for download.
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
# Optional: push to Hugging Face Hub.
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
# Optional: upload to Edge Impulse.
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: # noqa: BLE001 - surface errors to the UI
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()