# 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`);