# How the eSpeak NG data shipped at /voices/runtime/espeak-en-1.0.0/ was made (MafiaVoice, GPL-3.0-or-later, §6).
# Input: piper_phonemize.{js,data,wasm} of @diffusionstudio/piper-wasm 1.0.0 (https://github.com/diffusion-studio/piper-wasm),
# eSpeak NG data inside it; the exact eSpeak NG commit is not stated by that package (not verified further).
# Output: the same files with only en_dict, the phoneme tables, the intonations and lang/gmw/en* kept; the .wasm is unchanged.
# Run: node tools/voice/espeak-en-subset.mjs
# The script, verbatim (tools/voice/espeak-en-subset.mjs):
#!/usr/bin/env node
/* espeak-en-subset.mjs — DEVVOICE7b (08.10.2026, coordinator: «the en voices need an espeak phonemizer bundle of 18.7 MB»): the lab's
* piper_phonemize.data (@diffusionstudio/piper-wasm 1.0.0, espeak-ng data — GPL-3.0) carries ALL 113 espeak-ng languages. This writes an
* English-only copy: en_dict + the phoneme tables + intonations + the lang/gmw/en* voice files (no other dictionaries, no mbrola tables,
* no voices/ variants), and the matching emscripten glue (piper_phonemize.js with its package manifest rewritten). Measured, not shipped:
* the step-7 English path uses misaki's lexicons instead (no GPL on the device, and it runs on the phone too) — see the DEVVOICE7 report.
* node tools/voice/espeak-en-subset.mjs */
import { readFileSync, writeFileSync, mkdirSync, copyFileSync } from 'node:fs';
import { join } from 'node:path';
const [SRC, OUT] = process.argv.slice(2);
mkdirSync(OUT, { recursive: true });
const js = readFileSync(join(SRC, 'piper_phonemize.js'), 'utf8'), data = readFileSync(join(SRC, 'piper_phonemize.data'));
const m = /\{"files":(\[[^\]]*\]),"remote_package_size":(\d+)\}/.exec(js);
if (!m) throw new Error('package manifest not found in piper_phonemize.js');
const files = JSON.parse(m[1]);
export const keepEn = (n) => {
const b = n.replace('/espeak-ng-data/', '');
if (b.endsWith('_dict')) return b === 'en_dict';
if (b.startsWith('lang/')) return b.startsWith('lang/gmw/en');
if (b.startsWith('voices/') || b.startsWith('mbrola_ph/')) return false;
return true;
};
const parts = []; let at = 0; const keep = [];
for (const f of files) { if (!keepEn(f.filename)) continue; const b = data.subarray(f.start, f.end); keep.push({ ...f, start: at, end: at + b.length }); parts.push(b); at += b.length; }
writeFileSync(join(OUT, 'piper_phonemize.data'), Buffer.concat(parts));
writeFileSync(join(OUT, 'piper_phonemize.js'), js.replace(m[0], JSON.stringify({ files: keep, remote_package_size: at })));
copyFileSync(join(SRC, 'piper_phonemize.wasm'), join(OUT, 'piper_phonemize.wasm'));
console.log(`all languages: ${files.length} files, ${data.length} B → English only: ${keep.length} files, ${at} B`);