ngano 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ngano-0.1.0/.gitignore +7 -0
- ngano-0.1.0/CHANGELOG.md +75 -0
- ngano-0.1.0/PKG-INFO +376 -0
- ngano-0.1.0/README.md +335 -0
- ngano-0.1.0/hatch_build.py +37 -0
- ngano-0.1.0/pyproject.toml +89 -0
- ngano-0.1.0/snippets.json +37 -0
- ngano-0.1.0/src/ngano/__init__.py +92 -0
- ngano-0.1.0/src/ngano/auth.py +41 -0
- ngano-0.1.0/src/ngano/catalogue.py +614 -0
- ngano-0.1.0/src/ngano/cli.py +413 -0
- ngano-0.1.0/src/ngano/data/catalogue.json +35399 -0
- ngano-0.1.0/src/ngano/data/countries.json +582 -0
- ngano-0.1.0/src/ngano/data/field_map.json +275 -0
- ngano-0.1.0/src/ngano/data/language_codes.json +4373 -0
- ngano-0.1.0/src/ngano/data/languages.json +16380 -0
- ngano-0.1.0/src/ngano/data_files.py +80 -0
- ngano-0.1.0/src/ngano/errors.py +96 -0
- ngano-0.1.0/src/ngano/filters.py +573 -0
- ngano-0.1.0/src/ngano/languages.py +478 -0
- ngano-0.1.0/src/ngano/loader.py +686 -0
- ngano-0.1.0/src/ngano/mapping.py +532 -0
- ngano-0.1.0/src/ngano/models.py +456 -0
- ngano-0.1.0/src/ngano/py.typed +0 -0
- ngano-0.1.0/sync_data.py +115 -0
- ngano-0.1.0/tests/conftest.py +253 -0
- ngano-0.1.0/tests/test_audio.py +125 -0
- ngano-0.1.0/tests/test_catalogue.py +190 -0
- ngano-0.1.0/tests/test_cli.py +215 -0
- ngano-0.1.0/tests/test_doctests.py +44 -0
- ngano-0.1.0/tests/test_filters.py +78 -0
- ngano-0.1.0/tests/test_languages.py +345 -0
- ngano-0.1.0/tests/test_loader.py +388 -0
- ngano-0.1.0/tests/test_mapping.py +240 -0
- ngano-0.1.0/tests/test_snippets.py +147 -0
ngano-0.1.0/.gitignore
ADDED
ngano-0.1.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the `ngano` Python package are recorded here. The format
|
|
4
|
+
follows Keep a Changelog, and the project uses semantic versioning.
|
|
5
|
+
|
|
6
|
+
## [0.1.0] - 2026-09-13
|
|
7
|
+
|
|
8
|
+
First release.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `Catalogue`, reading the snapshot bundled in the wheel with no network and no
|
|
13
|
+
Hugging Face token, or reading live from `https://ngano.dev/api/v1` through
|
|
14
|
+
`Catalogue.from_api()`. 611 dataset records, 58 countries, 315 language tags.
|
|
15
|
+
- `Catalogue.datasets()`, `get()`, `find()`, `search()`, `countries()`,
|
|
16
|
+
`country()`, `languages()`, `language()`, `language_codes()`,
|
|
17
|
+
`resolve_language()` and `stats()`. Every hours total
|
|
18
|
+
excludes records flagged `unverified_size`, and `stats()` reports how many
|
|
19
|
+
records and hours were excluded.
|
|
20
|
+
- ISO 639-3 language keying. Every language is a BCP 47 tag whose primary
|
|
21
|
+
subtag is a three-letter code, with an optional region subtag for a
|
|
22
|
+
country-specific variety, so Shona is `sna` and Nigerian English is `eng-NG`.
|
|
23
|
+
`ngano.languages` bundles the registry as `language_codes.json` and resolves a
|
|
24
|
+
filter value in one fixed order: an exact tag case insensitively, then a bare
|
|
25
|
+
code that exists only as regional varieties, then a name from the registry
|
|
26
|
+
aliases in its plain or slugified spelling. A tag always beats a name, a bare
|
|
27
|
+
code widens to its varieties only with `include_varieties=True`, and an
|
|
28
|
+
unresolvable value matches nothing rather than being guessed at.
|
|
29
|
+
- `LanguageCode`, `LanguageRegistry`, `resolve_language()`,
|
|
30
|
+
`resolve_languages()`, `canonicalise_tag()` and `slugify_name()`, exported
|
|
31
|
+
from the package root.
|
|
32
|
+
- `DatasetRecord.language_tags`, `.language_codes` and `.language_note`, and a
|
|
33
|
+
`LanguageRecord` carrying `tag`, `iso639_3`, `region`, `scope`, `type`,
|
|
34
|
+
`aliases`, `country_codes` and `tasks` alongside its counts.
|
|
35
|
+
- `Row.language_tag`, resolved from the row's own `language_tag`, `language_iso`
|
|
36
|
+
or `language` column, taking the first that resolves to exactly one tag, and
|
|
37
|
+
otherwise from the catalogue record only when that record names exactly one
|
|
38
|
+
language, so a row of a multilingual corpus is never attributed to whichever
|
|
39
|
+
language the record happens to list first. `Row.language_iso` is that tag's
|
|
40
|
+
bare code, or the code the source stated kept verbatim when ngano cannot place
|
|
41
|
+
it, and `Row.language` keeps the source's own value.
|
|
42
|
+
- `Filter`, mirroring the `/datasets` query parameters, buildable from keyword
|
|
43
|
+
arguments or fluently, with `matches()`, `apply()` and `to_params()`.
|
|
44
|
+
`commercial=True` means `"Yes"`, and `include_purchasable=True` widens it to
|
|
45
|
+
`"Yes, if purchased"`. `language` and `iso` accept tags, bare codes and names
|
|
46
|
+
alike and are OR'd into one set of tags, and
|
|
47
|
+
`Filter.resolved_language_tags()` reports what a filter will match.
|
|
48
|
+
- `load()`, streaming canonical rows across every matching dataset at once, with
|
|
49
|
+
`round_robin`, `sequential` and `weighted_by_hours` interleaving, a row
|
|
50
|
+
`limit`, and `stream.matched` and `stream.loadable` counts. It warns once when
|
|
51
|
+
far fewer matched records are streamable than matched.
|
|
52
|
+
- `load_dataset()`, streaming one Hugging Face repo with the same canonical rows,
|
|
53
|
+
including repos that are not in the catalogue.
|
|
54
|
+
- `Row` and `AudioRef` dataclasses for the canonical schema shared by every ngano
|
|
55
|
+
SDK. `AudioRef.read()` fetches bytes only when called, and `AudioRef.decode()`
|
|
56
|
+
raises a clear error naming the `ngano[audio]` extra when soundfile is missing.
|
|
57
|
+
- Column mapping from `field_map.json` in the specified order: repo overrides,
|
|
58
|
+
runtime alias matching that ignores case, underscores and hyphens, unit hint
|
|
59
|
+
conversions, extras preserved in `Row.extra`, and the drop list discarded. An
|
|
60
|
+
unrecognised schema never raises; the reason lands in
|
|
61
|
+
`Row.extra["_ngano_unmapped"]`.
|
|
62
|
+
- Token resolution from the `hf_token` argument, then `HF_TOKEN`, then
|
|
63
|
+
`HUGGING_FACE_HUB_TOKEN`, then anonymous access, and `GatedDatasetError`
|
|
64
|
+
naming the repo and its access request URL instead of a raw 401.
|
|
65
|
+
- `ngano` command line interface built on argparse with `search`, `show`,
|
|
66
|
+
`countries`, `languages`, `stats` and `load`, and `--json` on every command.
|
|
67
|
+
`ngano languages` lists tags with their codes and names, `ngano show` prints a
|
|
68
|
+
record's tags, and `--include-varieties` widens a bare code.
|
|
69
|
+
- `sync_data.py` and a hatchling build hook that copy the repository data files
|
|
70
|
+
into the package at build time, with a `--check` mode for CI.
|
|
71
|
+
- `snippets.json`, the copy-pasteable Python examples the website and the MCP
|
|
72
|
+
`get_loader_snippet` tool serve, checked in CI against the shipped API.
|
|
73
|
+
- Full type hints, a `py.typed` marker, and an offline pytest suite.
|
|
74
|
+
|
|
75
|
+
[0.1.0]: https://github.com/thisisisheanesu/ngano/releases/tag/python-v0.1.0
|
ngano-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: ngano
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Open catalogue and streaming loader for African-language speech datasets
|
|
5
|
+
Project-URL: Homepage, https://ngano.dev
|
|
6
|
+
Project-URL: Documentation, https://ngano.dev/docs
|
|
7
|
+
Project-URL: API, https://ngano.dev/api/v1
|
|
8
|
+
Project-URL: Source, https://github.com/thisisisheanesu/ngano
|
|
9
|
+
Author: Isheanesu Nigel Misi
|
|
10
|
+
License: MIT
|
|
11
|
+
Keywords: africa,african languages,asr,corpus,datasets,huggingface,speech,tts
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
24
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.9
|
|
28
|
+
Requires-Dist: datasets>=2.14
|
|
29
|
+
Requires-Dist: requests>=2.28
|
|
30
|
+
Provides-Extra: all
|
|
31
|
+
Requires-Dist: numpy>=1.21; extra == 'all'
|
|
32
|
+
Requires-Dist: soundfile>=0.12; extra == 'all'
|
|
33
|
+
Provides-Extra: audio
|
|
34
|
+
Requires-Dist: numpy>=1.21; extra == 'audio'
|
|
35
|
+
Requires-Dist: soundfile>=0.12; extra == 'audio'
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest>=7.4; extra == 'dev'
|
|
39
|
+
Requires-Dist: types-requests>=2.28; extra == 'dev'
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
# ngano
|
|
43
|
+
|
|
44
|
+
**Open catalogue and streaming loader for African-language speech datasets.**
|
|
45
|
+
|
|
46
|
+
`ngano` (Shona: folk stories, the oral tradition) catalogues 611 speech and text
|
|
47
|
+
corpora for African languages, and streams the 283 of them that live on Hugging
|
|
48
|
+
Face through one canonical row schema. One loop can read across datasets that
|
|
49
|
+
agree on nothing: different column names, different units, different splits.
|
|
50
|
+
|
|
51
|
+
The catalogue ships inside the package. `Catalogue()` works offline, with no
|
|
52
|
+
network and no Hugging Face token.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install ngano # catalogue plus streaming loader
|
|
56
|
+
pip install "ngano[audio]" # adds soundfile and numpy for AudioRef.decode()
|
|
57
|
+
pip install "ngano[all]" # everything
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## The catalogue
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from ngano import Catalogue
|
|
64
|
+
|
|
65
|
+
cat = Catalogue() # bundled snapshot, offline
|
|
66
|
+
cat = Catalogue.from_api() # live from https://ngano.dev/api/v1
|
|
67
|
+
|
|
68
|
+
cat.datasets(language="sna", commercial=True, task="ASR")
|
|
69
|
+
cat.get("waxal-corpus-paper") # also accepts a repo id: cat.get("google/fleurs")
|
|
70
|
+
cat.search("parliament")
|
|
71
|
+
cat.countries()
|
|
72
|
+
cat.languages()
|
|
73
|
+
cat.language_codes() # the ISO 639-3 registry filters resolve through
|
|
74
|
+
cat.resolve_language("isiZulu") # -> "zul"
|
|
75
|
+
cat.stats()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Every record is a `DatasetRecord` with the catalogue fields: `id, name, task,
|
|
79
|
+
variety, languages, languages_clean, language_tags, language_codes, iso,
|
|
80
|
+
countries, country_codes, regions, hours, hours_num, speakers, recording_type,
|
|
81
|
+
quality, labelled, domain, licence, licence_class, commercial, access, host,
|
|
82
|
+
url, hf_repo, year, notes, language_note, unverified_size`.
|
|
83
|
+
|
|
84
|
+
`languages` keeps each source's own spelling, `languages_clean` the canonical
|
|
85
|
+
display names, and `language_tags` the BCP 47 tags ngano filters on.
|
|
86
|
+
`language_codes` holds the bare ISO 639-3 codes behind those tags. The eleven
|
|
87
|
+
records whose source describes its coverage in prose, for example "~340 African
|
|
88
|
+
languages", carry no tags and explain themselves in `language_note`.
|
|
89
|
+
|
|
90
|
+
## Languages
|
|
91
|
+
|
|
92
|
+
A language is a BCP 47 tag whose primary subtag is an ISO 639-3 three-letter
|
|
93
|
+
code. An optional region subtag marks a country-specific variety, so Shona is
|
|
94
|
+
`sna`, Nigerian English is `eng-NG` and Mozambican Portuguese is `por-MZ`. There
|
|
95
|
+
are 315 tags, 27 of them regional.
|
|
96
|
+
|
|
97
|
+
Any filter, lookup or CLI flag that takes a language accepts three spellings,
|
|
98
|
+
resolved in this order, first match wins:
|
|
99
|
+
|
|
100
|
+
1. an exact tag, case insensitively, canonicalised to a lowercase primary
|
|
101
|
+
subtag and an uppercase region, so `sna`, `SNA`, `eng-NG` and `eng-ng` all
|
|
102
|
+
land on the same key;
|
|
103
|
+
2. a bare ISO 639-3 code that exists only as regional varieties, which resolves
|
|
104
|
+
to those varieties, since there is nothing else it could mean;
|
|
105
|
+
3. a name from the registry aliases, case insensitively, in its plain or its
|
|
106
|
+
slugified spelling, so `language="isiZulu"` still finds `zul`.
|
|
107
|
+
|
|
108
|
+
A tag always beats a name. The one collision in the catalogue is `tem`, which is
|
|
109
|
+
the tag of Timne and also the name of `kdh`, and the tag wins.
|
|
110
|
+
|
|
111
|
+
A bare code never widens to its regional varieties on its own. Ask for that with
|
|
112
|
+
`include_varieties=True`, which returns the bare tag first and then every
|
|
113
|
+
`<code>-*` in registry order. A value that already names a regional variety has
|
|
114
|
+
nothing to widen to, so `include_varieties` leaves it alone, whether it was given
|
|
115
|
+
as a tag or as a name. A value that resolves to nothing matches nothing, because
|
|
116
|
+
ngano does not guess.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from ngano import resolve_language
|
|
120
|
+
|
|
121
|
+
resolve_language("SNA") # ["sna"]
|
|
122
|
+
resolve_language("isiZulu") # ["zul"]
|
|
123
|
+
resolve_language("eng", include_varieties=True) # ["eng", "eng-NG", "eng-ZA", ...]
|
|
124
|
+
resolve_language("Nigerian English", include_varieties=True) # ["eng-NG"], nothing to widen
|
|
125
|
+
resolve_language("Many African languages") # []
|
|
126
|
+
|
|
127
|
+
cat.datasets(language="eng-NG") # Nigerian English only
|
|
128
|
+
cat.datasets(language="eng", include_varieties=True)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`cat.languages()` returns a `LanguageRecord` per tag with `tag, iso639_3,
|
|
132
|
+
region, name, scope, type, aliases, slug, datasets, hours, countries,
|
|
133
|
+
country_codes, tasks`. Hours are apportioned evenly across each record's tags
|
|
134
|
+
and exclude the unverified figures. `cat.language("Shona")`, `cat.language("sna")`
|
|
135
|
+
and `cat.language("SNA")` all return the same record.
|
|
136
|
+
|
|
137
|
+
Hours are reproduced as each source published them. Self-reported figures of
|
|
138
|
+
20,000 hours or more carry `unverified_size: true` and are excluded from every
|
|
139
|
+
total that ngano computes, in `stats()`, in `countries()` and in `languages()`.
|
|
140
|
+
`record.countable_hours` is the figure that may be summed.
|
|
141
|
+
|
|
142
|
+
## Filters
|
|
143
|
+
|
|
144
|
+
A `Filter` mirrors the query parameters of `GET /api/v1/datasets`. Values inside
|
|
145
|
+
one parameter are OR'd, and parameters are AND'd. Build it with keywords or
|
|
146
|
+
fluently, whichever reads better:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from ngano import Filter
|
|
150
|
+
|
|
151
|
+
Filter(language=["sna", "nde"], country="ZW", task="ASR", commercial=True)
|
|
152
|
+
Filter().language("sna", "nde").country("ZW").task("ASR").commercial()
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`commercial=True` means `commercial == "Yes"`. Add `include_purchasable=True` to
|
|
156
|
+
also accept `"Yes, if purchased"`:
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
cat.datasets(commercial=True, include_purchasable=True)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Other parameters: `q, iso, region, variety, licence_class, access, labelled,
|
|
163
|
+
quality, domain, host, hf_only, min_hours, max_hours, has_hours,
|
|
164
|
+
include_varieties, sort`. `iso` is the code-shaped spelling of `language`, and
|
|
165
|
+
the two are OR'd into one set of tags rather than AND'd.
|
|
166
|
+
`Filter.resolved_language_tags()` shows exactly which tags a filter will match,
|
|
167
|
+
and `Filter.to_params()` serialises to the HTTP query string if you want to call
|
|
168
|
+
the API yourself.
|
|
169
|
+
|
|
170
|
+
## Streaming
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from ngano import load
|
|
174
|
+
|
|
175
|
+
stream = load(
|
|
176
|
+
language=["Shona", "Ndebele"],
|
|
177
|
+
country="ZW",
|
|
178
|
+
commercial=True,
|
|
179
|
+
task="ASR",
|
|
180
|
+
split="train",
|
|
181
|
+
streaming=True,
|
|
182
|
+
hf_token=None,
|
|
183
|
+
limit=None,
|
|
184
|
+
interleave="round_robin",
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
print(stream.matched, "datasets matched,", stream.loadable, "can be streamed")
|
|
188
|
+
|
|
189
|
+
for row in stream:
|
|
190
|
+
print(row.transcript, row.language, row.duration_s)
|
|
191
|
+
if row.audio:
|
|
192
|
+
print(row.audio.url, row.audio.path, row.audio.sampling_rate)
|
|
193
|
+
break # nothing further is fetched
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Nothing is materialised. Each dataset is opened with
|
|
197
|
+
`datasets.load_dataset(..., streaming=True)`, and rows are mapped as they
|
|
198
|
+
arrive. Breaking out of the loop leaves the rest of every dataset untouched.
|
|
199
|
+
|
|
200
|
+
`stream.matched` is how many catalogue records satisfied the filter, and
|
|
201
|
+
`stream.loadable` is how many of those have a Hugging Face repo. Only those can
|
|
202
|
+
be streamed; the rest are catalogued sources you have to obtain from their host.
|
|
203
|
+
When the gap is large, `load()` warns once. Pass `hf_only=True` to filter them
|
|
204
|
+
out up front.
|
|
205
|
+
|
|
206
|
+
### Interleaving
|
|
207
|
+
|
|
208
|
+
- `round_robin` (default): one row from each dataset in turn.
|
|
209
|
+
- `sequential`: one dataset to exhaustion, then the next.
|
|
210
|
+
- `weighted_by_hours`: rows in proportion to each corpus's published hours,
|
|
211
|
+
deterministic, no randomness.
|
|
212
|
+
|
|
213
|
+
`datasets.interleave_datasets` is used when the datasets share one schema, which
|
|
214
|
+
is what that function requires. Otherwise ngano round robins them itself. Either
|
|
215
|
+
way the merge is lazy.
|
|
216
|
+
|
|
217
|
+
### One repo at a time
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
from ngano import load_dataset
|
|
221
|
+
|
|
222
|
+
for row in load_dataset("google/fleurs", config="sw_ke", limit=5):
|
|
223
|
+
print(row.language_tag, row.language_iso, row.transcript)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
A repo that is not in the catalogue still streams. `row.dataset_id`,
|
|
227
|
+
`row.licence` and the other catalogue fields are then `None`.
|
|
228
|
+
|
|
229
|
+
## The canonical row
|
|
230
|
+
|
|
231
|
+
Every row is a `Row` dataclass, identical across the Python, JavaScript and Rust
|
|
232
|
+
SDKs:
|
|
233
|
+
|
|
234
|
+
| field | type | meaning |
|
|
235
|
+
| --- | --- | --- |
|
|
236
|
+
| `audio` | `AudioRef \| None` | lazy handle, never decoded by default |
|
|
237
|
+
| `transcript` | `str \| None` | reference text |
|
|
238
|
+
| `language` | `str \| None` | language name as the source labels it, else the catalogue's canonical name |
|
|
239
|
+
| `language_iso` | `str \| None` | bare ISO 639-3 code, see below |
|
|
240
|
+
| `language_tag` | `str \| None` | BCP 47 tag, for example `sna` or `eng-NG`, see below |
|
|
241
|
+
| `country` | `str \| None` | ISO 3166-1 alpha-2 |
|
|
242
|
+
| `speaker_id` | `str \| None` | speaker id within the dataset |
|
|
243
|
+
| `gender` | `str \| None` | as stated by the source, never inferred |
|
|
244
|
+
| `age` | `str \| None` | as stated by the source, never inferred |
|
|
245
|
+
| `duration_s` | `float \| None` | seconds |
|
|
246
|
+
| `sampling_rate` | `int \| None` | Hz |
|
|
247
|
+
| `domain` | `str \| None` | recording domain |
|
|
248
|
+
| `split` | `str` | split name |
|
|
249
|
+
| `dataset_id` | `str \| None` | ngano catalogue id |
|
|
250
|
+
| `hf_repo` | `str \| None` | Hugging Face repo |
|
|
251
|
+
| `licence` | `str \| None` | licence from the catalogue |
|
|
252
|
+
| `source_url` | `str \| None` | canonical dataset URL |
|
|
253
|
+
| `extra` | `dict` | every column ngano did not map |
|
|
254
|
+
|
|
255
|
+
`row.to_dict()` gives a JSON-serialisable dict. Audio bytes are never included.
|
|
256
|
+
|
|
257
|
+
### How a row gets its language
|
|
258
|
+
|
|
259
|
+
`language_tag` is, in order:
|
|
260
|
+
|
|
261
|
+
1. the row's own `language_tag`, `language_iso` or `language` column, taking the
|
|
262
|
+
first that resolves to exactly one tag;
|
|
263
|
+
2. otherwise the catalogue record's tag, but only when that record names exactly
|
|
264
|
+
one language;
|
|
265
|
+
3. otherwise `None`.
|
|
266
|
+
|
|
267
|
+
A row from a forty language corpus is not attributed to one language just
|
|
268
|
+
because the record lists that language first, so a silent row of a multilingual
|
|
269
|
+
record has `language_tag=None`.
|
|
270
|
+
|
|
271
|
+
`language_iso` is the bare primary subtag of `language_tag` when the row's own
|
|
272
|
+
value resolved, otherwise the code the source stated kept verbatim when ngano
|
|
273
|
+
cannot place it, so an ISO 639-1 `sw` survives as `"sw"`, otherwise the record's
|
|
274
|
+
code when it names one language. `language` always keeps the source's own
|
|
275
|
+
spelling, falling back to the canonical name of a single-language record.
|
|
276
|
+
|
|
277
|
+
## Lazy audio
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
row.audio.url # remote location, if the source gave one
|
|
281
|
+
row.audio.path # local or archive-relative path
|
|
282
|
+
row.audio.sampling_rate # Hz, as stated by the source
|
|
283
|
+
|
|
284
|
+
data = row.audio.read() # fetches bytes only now
|
|
285
|
+
array, sr = row.audio.decode() # needs pip install "ngano[audio]"
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`decode()` raises a clear `ImportError` naming the `ngano[audio]` extra when
|
|
289
|
+
soundfile is not installed. Building a row never reads, downloads or decodes
|
|
290
|
+
anything.
|
|
291
|
+
|
|
292
|
+
## Column mapping
|
|
293
|
+
|
|
294
|
+
Datasets do not agree on column names, so ngano maps them in one fixed order,
|
|
295
|
+
the same in every SDK:
|
|
296
|
+
|
|
297
|
+
1. `overrides[hf_repo]` from `field_map.json`, for repos that were checked by hand.
|
|
298
|
+
2. runtime inspection of the dataset's real columns against `aliases`, matched
|
|
299
|
+
case-insensitively and ignoring underscores and hyphens, so `Client_ID`,
|
|
300
|
+
`client-id` and `clientid` are the same column.
|
|
301
|
+
3. `unit_hints` conversions, so a `duration_ms` column of 2500 becomes
|
|
302
|
+
`duration_s == 2.5`.
|
|
303
|
+
4. anything still unmapped is preserved in `row.extra`, never dropped silently.
|
|
304
|
+
5. columns in the `drop` list are discarded rather than kept as extras.
|
|
305
|
+
|
|
306
|
+
An unknown schema never raises. `transcript` and `audio` stay `None` and the
|
|
307
|
+
reason is recorded in `row.extra["_ngano_unmapped"]`, naming the columns that
|
|
308
|
+
were seen. `stream.plans` shows how each repo was mapped once it has opened.
|
|
309
|
+
|
|
310
|
+
## Authentication
|
|
311
|
+
|
|
312
|
+
ngano itself needs no credentials. A token is only used for gated Hugging Face
|
|
313
|
+
repos, resolved in this order: the `hf_token` argument, then `HF_TOKEN`, then
|
|
314
|
+
`HUGGING_FACE_HUB_TOKEN`, then anonymous access. A gated repo raises
|
|
315
|
+
`GatedDatasetError` naming the repo and the page where access is requested,
|
|
316
|
+
rather than a raw 401.
|
|
317
|
+
|
|
318
|
+
## Command line
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
ngano search parliament --limit 5
|
|
322
|
+
ngano search --language sna --task ASR --commercial --json
|
|
323
|
+
ngano search --language isiZulu # a name works too
|
|
324
|
+
ngano search --language eng --include-varieties # eng plus every eng-* variety
|
|
325
|
+
ngano show google/fleurs
|
|
326
|
+
ngano countries --region "Southern Africa"
|
|
327
|
+
ngano languages --min-datasets 10 # tag, code, name, counts
|
|
328
|
+
ngano stats --json
|
|
329
|
+
ngano load --language sna --hf-only --limit 20 --out rows.jsonl
|
|
330
|
+
ngano load --language sna --hf-only --dry-run
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
`--json` works on every command, before or after the subcommand. `--api` reads
|
|
334
|
+
the live API instead of the bundled snapshot. `ngano languages` lists one line
|
|
335
|
+
per tag, with the bare ISO 639-3 code and the canonical name, and `ngano show`
|
|
336
|
+
prints a record's `language_tags` and `language_codes`.
|
|
337
|
+
|
|
338
|
+
## Updating the bundled data
|
|
339
|
+
|
|
340
|
+
The wheel carries a snapshot of `catalogue.json`, `countries.json`,
|
|
341
|
+
`languages.json`, `language_codes.json` and `field_map.json` copied from the
|
|
342
|
+
repository root. The
|
|
343
|
+
hatchling hook in `hatch_build.py` runs the copy during `python -m build`, so a
|
|
344
|
+
normal build is always current. To refresh it by hand, or to check for drift in
|
|
345
|
+
CI:
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
python sync_data.py # copy data/*.json into src/ngano/data/
|
|
349
|
+
python sync_data.py --check # exit 1 if the snapshot is stale
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## Snippets for the website
|
|
353
|
+
|
|
354
|
+
`snippets.json` holds the Python examples that ngano.dev and the MCP
|
|
355
|
+
`get_loader_snippet` tool serve, with `{{LANGUAGE}}`, `{{COUNTRY_ISO2}}`,
|
|
356
|
+
`{{COUNTRY_NAME}}`, `{{DATASET_ID}}`, `{{HF_REPO}}`, `{{CONFIG}}` and `{{TASK}}`
|
|
357
|
+
as the substitution tokens. Each token is described in `placeholder_docs`.
|
|
358
|
+
`{{LANGUAGE}}` is a BCP 47 tag such as `sna`, never a display name, because that
|
|
359
|
+
is what the filters take, and the website and the MCP server substitute a tag.
|
|
360
|
+
The test suite substitutes real values and compiles every snippet, and runs the
|
|
361
|
+
offline ones, so they cannot drift from this API.
|
|
362
|
+
|
|
363
|
+
## Development
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
pip install -e ".[dev,audio]"
|
|
367
|
+
pytest # the whole suite is offline, no network, no token
|
|
368
|
+
mypy # strict, on the public surface
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## Licences and credits
|
|
372
|
+
|
|
373
|
+
Code MIT. Catalogue data CC-BY-4.0. By Isheanesu Nigel Misi.
|
|
374
|
+
The catalogue is a list of sources, with figures as published by each source and
|
|
375
|
+
unverified ones flagged. It is not a claim that every corpus is usable, legal
|
|
376
|
+
for your purpose, or of the quality its publisher states.
|