File size: 4,869 Bytes
5838d6a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
"""Hugging Face dataset export: audio layout, metadata, card, optional push."""

from __future__ import annotations

import csv
import json
import shutil
from pathlib import Path
from typing import Dict, List, Optional

from .builder import BuildResult
from .config import DatasetConfig


def _dataset_card(config: DatasetConfig, result: BuildResult, repo_id: str) -> str:
    labels_table = "\n".join(
        f"| `{label}` | {count} |" for label, count in sorted(result.label_counts.items())
    )
    return f"""---
license: cc-by-4.0
pretty_name: {config.dataset_name} wake word synthetic speech dataset
task_categories:
- audio-classification
tags:
- audio
- speech
- wake-word
- keyword-spotting
- text-to-speech
- synthetic-data
- edge-impulse
- tinyml
size_categories:
- n<1K
---

# {config.dataset_name} — Wake Word Synthetic Speech Dataset

Synthetic, augmented audio for training a small wake-word / keyword-spotting
model. Generated with **{result.backend_source}** and local audio augmentation.

## Classes

| Label | Samples |
|---|---|
{labels_table}

- `{config.wake_label}` — the target wake phrase and close variants.
- `{config.unknown_label}` — near-miss and unrelated short phrases.
- `{config.noise_label}` — synthetic background noise.

## Audio Specification

| Property | Value |
|---|---|
| Format | WAV |
| Channels | Mono |
| Sample rate | {config.sample_rate_hz} Hz |
| Clip length | {config.duration_seconds} seconds |

## Layout

```text
audio/
  train/  {config.wake_label}.<id>.wav ...
  test/   {config.wake_label}.<id>.wav ...
metadata.csv
hf_metadata.csv
selected_voices.csv
dataset_summary.json
```

## Loading

```python
from datasets import load_dataset, Audio

ds = load_dataset("{repo_id}")
ds = ds.cast_column("audio", Audio(sampling_rate={config.sample_rate_hz}))
print(ds)
```

## Edge Impulse

Filenames follow the Edge Impulse label-prefix convention
(`{config.wake_label}.<id>.wav`) so they upload directly:

```bash
edge-impulse-uploader --category training audio/train/*.wav
edge-impulse-uploader --category testing audio/test/*.wav
```

## Limitations

Synthetic TTS is a bootstrap, not a production benchmark. Add real device
and environment recordings before deploying a wake-word product.

## License

CC BY 4.0. Verify that your use of the generated synthetic speech complies
with the terms of the voice models and tools used to create it.
"""


def export_hf_dataset(
    config: DatasetConfig,
    result: BuildResult,
    hf_dir: str,
    repo_id: str = "your-username/your-dataset",
) -> str:
    """Build a Hugging Face-ready folder from a completed build. Returns its path."""
    source_dir = Path(result.out_dir)
    hf_path = Path(hf_dir).resolve()
    if hf_path.exists():
        shutil.rmtree(hf_path)
    (hf_path / "audio" / "train").mkdir(parents=True, exist_ok=True)
    (hf_path / "audio" / "test").mkdir(parents=True, exist_ok=True)

    for src_split, hf_split in (("training", "train"), ("testing", "test")):
        src = source_dir / "edge_impulse_upload" / src_split
        dst = hf_path / "audio" / hf_split
        for wav in sorted(src.glob("*.wav")):
            shutil.copy2(wav, dst / wav.name)

    for name in ("metadata.csv", "selected_voices.csv", "dataset_summary.json"):
        src = source_dir / name
        if src.exists():
            shutil.copy2(src, hf_path / name)

    # HF metadata.csv-style index (audio path + label).
    hf_rows: List[Dict[str, str]] = []
    for split in ("train", "test"):
        for wav in sorted((hf_path / "audio" / split).glob("*.wav")):
            hf_rows.append(
                {
                    "audio": str(wav.relative_to(hf_path)),
                    "label": wav.name.split(".")[0],
                    "split": split,
                    "filename": wav.name,
                }
            )
    with (hf_path / "hf_metadata.csv").open("w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=["audio", "label", "split", "filename"])
        writer.writeheader()
        writer.writerows(hf_rows)

    (hf_path / "README.md").write_text(
        _dataset_card(config, result, repo_id), encoding="utf-8"
    )
    (hf_path / "hf_dataset_summary.json").write_text(
        json.dumps({"total_wavs": len(hf_rows), "repo_id": repo_id}, indent=2),
        encoding="utf-8",
    )
    return str(hf_path)


def push_to_hub(hf_dir: str, repo_id: str, token: str, private: bool = False) -> str:
    """Upload the HF dataset folder to the Hub. Returns the dataset URL."""
    from huggingface_hub import HfApi

    api = HfApi(token=token)
    api.create_repo(repo_id=repo_id, repo_type="dataset", exist_ok=True, private=private)
    api.upload_folder(
        folder_path=hf_dir,
        repo_id=repo_id,
        repo_type="dataset",
    )
    return f"https://huggingface.co/datasets/{repo_id}"