Church Translate, as it runs at the campuses
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>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* app-log.js — the persistent log this app never had.
|
||||
*
|
||||
* Every child (voice process, congregation server, translation model) writes
|
||||
* to stdout, and main.js forwards that to ITS stdout — which, for an app
|
||||
* launched from the Finder, is nowhere. So the morning a service went wrong
|
||||
* there was a caption transcript and not one line saying what the voice
|
||||
* process had decided to do. This module tees everything to
|
||||
* <dataDir>/logs/app.log, rotated so it can never fill a disk.
|
||||
*
|
||||
* Deliberately boring: synchronous appends (a crash must not lose the line
|
||||
* that explains it), a size cap, three generations, and every failure
|
||||
* swallowed — logging must never be the thing that takes the service down.
|
||||
*/
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const MAX_BYTES = 2 * 1024 * 1024; // rotate past 2 MB
|
||||
const KEEP = 3; // app.log, app.log.1, app.log.2
|
||||
|
||||
function createAppLog(dir, name) {
|
||||
name = name || 'app.log';
|
||||
const file = path.join(dir, name);
|
||||
let size = 0;
|
||||
try { fs.mkdirSync(dir, { recursive: true }); } catch (_) {}
|
||||
try { size = fs.statSync(file).size; } catch (_) { size = 0; }
|
||||
|
||||
function rotate() {
|
||||
try {
|
||||
for (let i = KEEP - 1; i >= 1; i--) {
|
||||
const from = file + (i === 1 ? '' : '.' + (i - 1));
|
||||
const to = file + '.' + i;
|
||||
try { fs.renameSync(from, to); } catch (_) {}
|
||||
}
|
||||
} catch (_) {}
|
||||
size = 0;
|
||||
}
|
||||
|
||||
function stamp() {
|
||||
const d = new Date();
|
||||
const p = (n, w) => String(n).padStart(w || 2, '0');
|
||||
return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate()) + ' ' +
|
||||
p(d.getHours()) + ':' + p(d.getMinutes()) + ':' + p(d.getSeconds()) + '.' + p(d.getMilliseconds(), 3);
|
||||
}
|
||||
|
||||
/* Write one or more lines under a source tag. Multi-line chunks from a
|
||||
child's stdout are split so every line carries its own stamp and tag. */
|
||||
function write(tag, chunk) {
|
||||
try {
|
||||
const text = String(chunk == null ? '' : chunk);
|
||||
if (!text.trim()) return;
|
||||
const lines = text.replace(/\r/g, '').split('\n').filter((l) => l.length);
|
||||
const out = lines.map((l) => stamp() + ' [' + tag + '] ' + l).join('\n') + '\n';
|
||||
/* Bytes, not characters: accented Spanish and CJK log lines are 2-3
|
||||
bytes a character, and a character count let a generation reach 6 MB. */
|
||||
const bytes = Buffer.byteLength(out, 'utf8');
|
||||
if (size + bytes > MAX_BYTES) rotate();
|
||||
fs.appendFileSync(file, out);
|
||||
size += bytes;
|
||||
} catch (_) { /* never let logging take anything down */ }
|
||||
}
|
||||
|
||||
/* Attach to a child process: tee stdout/stderr to the file. Returns the
|
||||
child for chaining. The caller may still forward to its own stdout. */
|
||||
function tee(child, tag) {
|
||||
if (child && child.stdout) child.stdout.on('data', (d) => write(tag, d));
|
||||
if (child && child.stderr) child.stderr.on('data', (d) => write(tag + ':err', d));
|
||||
return child;
|
||||
}
|
||||
|
||||
return { write, tee, file, rotate };
|
||||
}
|
||||
|
||||
module.exports = { createAppLog, MAX_BYTES, KEEP };
|
||||
Reference in New Issue
Block a user