Files
zeekay 237a080a80 brain: land fortemi parity sweep + 7 pure-CPU algorithm modules
Studied fortemi/fortemi @ v2026.5.0 and produced PARITY.md — every
algorithm Fortémi ships has a row mapping to a Hanzo destination
(this repo, a sibling hanzo/* repo, or a tracked planned module).

Landed under packages/memory/, all backed by 21 passing tests in
algorithms.test.ts:

- fusion.ts    RRF (k=20 BEIR), RSF, adaptive k, adaptive weights
- rerank.ts    Maximal Marginal Relevance (Carbonell & Goldstein 1998)
- dedup.ts     chunk-aware deduplication (best per chain)
- script.ts    Unicode script detection — Latin/CJK/emoji/Cyrillic/
               Arabic/Hebrew/Greek/Devanagari
- embed.ts     embedding model registry, asymmetric E5 prefixes,
               Matryoshka truncation, coarse-dim picker
- temporal.ts  UUIDv7 floor/ceiling + named ranges
- captions.ts  WebVTT / SRT / RTTM renderers

Exposed from @hanzo/bot-memory v1.4.3.

Remaining gaps tracked in PARITY.md: FTS feature flags, federated search,
runtime config hot-swap, capabilities/task selector, eval harness, SKOS,
FAIR export, SNN/PFNET/Louvain graph maintenance, LLM-typed graph links,
extraction registry — plus sibling-repo wire-ups (search/vector/crypto/
age/replicate/insights/agents).
2026-05-11 10:44:01 -07:00

73 lines
2.5 KiB
TypeScript

/**
* Caption / subtitle rendering from time-coded segments.
*
* Supports WebVTT (W3C), SRT (SubRip), and RTTM (NIST diarization).
*/
export interface CaptionSegment {
startSecs: number;
endSecs: number;
text: string;
/** Optional speaker label (used for diarization-aware output). */
speaker?: string;
}
// ── WebVTT ─────────────────────────────────────────────────────────────
export function renderVtt(segs: CaptionSegment[]): string {
let out = "WEBVTT\n\n";
segs.forEach((s, i) => {
out += `${i + 1}\n`;
out += `${vttTime(s.startSecs)} --> ${vttTime(s.endSecs)}\n`;
if (s.speaker) out += `<v ${s.speaker}>${s.text}</v>\n\n`;
else out += `${s.text}\n\n`;
});
return out;
}
function vttTime(secs: number): string {
const ms = Math.floor((secs - Math.floor(secs)) * 1000);
const total = Math.floor(secs);
const h = Math.floor(total / 3600);
const m = Math.floor((total % 3600) / 60);
const s = total % 60;
return `${pad2(h)}:${pad2(m)}:${pad2(s)}.${pad3(ms)}`;
}
// ── SRT ────────────────────────────────────────────────────────────────
export function renderSrt(segs: CaptionSegment[]): string {
let out = "";
segs.forEach((s, i) => {
out += `${i + 1}\n`;
out += `${srtTime(s.startSecs)} --> ${srtTime(s.endSecs)}\n`;
if (s.speaker) out += `${s.speaker}: ${s.text}\n\n`;
else out += `${s.text}\n\n`;
});
return out;
}
function srtTime(secs: number): string {
const ms = Math.floor((secs - Math.floor(secs)) * 1000);
const total = Math.floor(secs);
const h = Math.floor(total / 3600);
const m = Math.floor((total % 3600) / 60);
const s = total % 60;
return `${pad2(h)}:${pad2(m)}:${pad2(s)},${pad3(ms)}`;
}
// ── RTTM (NIST Rich Transcription Time Marked) ─────────────────────────
export function renderRttm(segs: CaptionSegment[], uri: string = "audio"): string {
let out = "";
for (const s of segs) {
if (!s.speaker) continue;
const duration = (s.endSecs - s.startSecs).toFixed(3);
out += `SPEAKER ${uri} 1 ${s.startSecs.toFixed(3)} ${duration} <NA> <NA> ${s.speaker} <NA> <NA>\n`;
}
return out;
}
function pad2(n: number): string { return n.toString().padStart(2, "0"); }
function pad3(n: number): string { return n.toString().padStart(3, "0"); }