// Riascolta le voci del gioco (make ascolta): ogni mp3 di assets/voices/ passa da `mbot ascolta` (faster-whisper sulla // GPU, skill «trascrittore») e si confronta col testo letto dalla voce. Elenca quelle che dicono altro: si rifanno // con `node tools/voci.mjs --rifai 'pezzo di testo'`, oppure si aggiunge una riga a tools/pronuncia.json. // node tools/ascolta.mjs [--min 0.8] [testo...] (con un testo: solo le battute che lo contengono) import { execFileSync } from 'node:child_process'; import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { spoken } from './pronuncia.mjs'; const ROOT = new URL('..', import.meta.url).pathname; const index = JSON.parse(readFileSync(join(ROOT, 'assets/voices/index.json'), 'utf8')); const args = process.argv.slice(2), at = args.indexOf('--min'); const min = at >= 0 ? args.splice(at, 2)[1] : '0.8'; const only = args; // I numeri Whisper li scrive in cifre («Nove.» → «9.»): per il confronto le cifre tornano parole. const NUMERI = ['zero', 'uno', 'due', 'tre', 'quattro', 'cinque', 'sei', 'sette', 'otto', 'nove', 'dieci', 'undici', 'dodici']; const plain = t => t.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[^a-z0-9]+/g, ' ').trim(); const jobs = Object.entries(index) .map(([key, e]) => ({ who: key.split('|')[0], text: key.slice(key.indexOf('|') + 1), file: join(ROOT, 'assets/voices', e.file) })) .filter(j => !only.length || only.some(o => j.text.includes(o))) .map(j => ({ ...j, expect: spoken(j.text) })); const dir = join(tmpdir(), 'leggenda-regolo-ascolto'); mkdirSync(dir, { recursive: true }); writeFileSync(join(dir, 'ascolto.json'), JSON.stringify(jobs.map(({ file, expect }) => ({ file, expect })))); let out = ''; try { out = execFileSync('mbot', ['ascolta', '--jobs', join(dir, 'ascolto.json'), '--json', '--min', min], { encoding: 'utf8', maxBuffer: 64 << 20, stdio: ['ignore', 'pipe', 'inherit'] }); } catch (e) { out = e.stdout ?? ''; } const byFile = new Map(jobs.map(j => [j.file, j])); const bad = []; for (const line of out.split('\n').filter(l => l.startsWith('{'))) { const r = JSON.parse(line); if (r.ok !== false) continue; const j = byFile.get(r.file), heard = r.text ?? r.heard ?? ''; if (j && plain(heard.replace(/\b(\d{1,2})\b/g, (m, n) => NUMERI[+n] ?? m)) === plain(j.expect)) continue; bad.push({ ...j, heard, score: r.score ?? r.similarity }); } for (const b of bad) console.log(`${b.score ?? '?'} ${b.who}: «${b.text}»\n sentito: «${b.heard}»`); console.log(`${jobs.length} voci ascoltate, ${bad.length} da guardare`);