eoinedge commited on
Commit
93042b3
·
verified ·
1 Parent(s): 5838d6a

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +245 -0
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()