Live sermon translation into 100+ languages, running entirely on one Mac per campus: audio in, voice detection, Whisper on the GPU, NLLB-200 in its own process, and a built-in web server the congregation reads from on their phones. No audio leaves the building. This is the source only. What used to sit beside it — 14 GB of speech and translation models, 8 GB of voice bundles, 6 GB of natural voices, the Electron caches, and the app data holding the operator PIN and every service transcript — is machine state and downloaded assets, and stays in the NAS backup where it belongs. The vendored node, cloudflared and Windows ffmpeg binaries are out too (533 MB, re-fetched for packaging); the browser-side runtime beside them is kept, so a checkout still serves the congregation page. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
40 lines
1.8 KiB
JavaScript
40 lines
1.8 KiB
JavaScript
/* The full chain, and specifically: does the ENGLISH the listener sees stay
|
|
exactly as spoken while the translation improves? */
|
|
import { pipeline, env } from '@huggingface/transformers';
|
|
import { createRequire } from 'module';
|
|
import os from 'os'; import path from 'path'; import fs from 'fs'; import vm from 'vm';
|
|
const require = createRequire(import.meta.url);
|
|
const Liturgy=require('./liturgy.js'), Guard=require('./mt-guard.js'), Preach=require('./preach.js');
|
|
const src=fs.readFileSync('engine.js','utf8');
|
|
const sb={console}; vm.createContext(sb);
|
|
vm.runInContext(src.match(/^const ABBREV_TAIL = .*$/m)[0], sb);
|
|
const grab=(n)=>{const i=src.indexOf('function '+n+'(');let d=0;for(let k=src.indexOf('{',i);k<src.length;k++){if(src[k]==='{')d++;else if(src[k]==='}'&&--d===0)return src.slice(i,k+1);}};
|
|
vm.runInContext(grab('splitSentences'), sb);
|
|
|
|
env.allowLocalModels=false;
|
|
env.cacheDir=path.join(os.homedir(),'Library/Application Support/Church Translate Models');
|
|
const pipe=await pipeline('translation','Xenova/nllb-200-distilled-600M',{dtype:'fp32'});
|
|
|
|
const UTTERANCES=[
|
|
'Open up to the book of John.',
|
|
'Circle that in your Bible. Amen.',
|
|
'Let that sink in.',
|
|
'Open up to the book of John. God is good. Amen.',
|
|
'We are going to talk about Peter in the Bible.'
|
|
];
|
|
for (const u of UTTERANCES) {
|
|
console.log('\nSPOKEN / shown in English: "'+u+'"');
|
|
for (const lang of ['spa_Latn','por_Latn','fra_Latn']) {
|
|
const parts=[];
|
|
for (const s of sb.splitSentences(u)) {
|
|
const set=Liturgy.lookup(s,lang);
|
|
if (set) { parts.push(set); continue; }
|
|
const forMt=Preach.forTranslation(s);
|
|
const o=await pipe(forMt,{src_lang:'eng_Latn',tgt_lang:lang});
|
|
const raw=(Array.isArray(o)?o[0]:o).translation_text.trim();
|
|
parts.push(Guard.clean(forMt,raw).text);
|
|
}
|
|
console.log(' '+lang.slice(0,3)+' '+parts.join(' '));
|
|
}
|
|
}
|