1const UNIVERSAL_FILLER_WORDS = [
2 "uh",
3 "uhm",
4 "umm",
5 "uhh",
6 "uhhh",
7 "ehh",
8 "ehm",
9 "ahm",
10 "hmm",
11 "hm",
12 "mmm",
13 "хм",
14 "ммм",
15] as const;
16
17const LANGUAGE_FILLER_WORDS: Readonly<Record<string, readonly string[]>> = {
18 en: ["um", "ah", "eh"],
19 de: ["äh", "ähm"],
20 fr: ["euh"],
21};
22
23const escapeRegex = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
24
25export function removeSpeechFillerWords(
26 text: string,
27 language?: string,
28 correctionCue?: string,
29): string {
30 const baseLanguage = language?.split(/[-_]/, 1)[0]?.toLowerCase();
31 const words = [
32 ...UNIVERSAL_FILLER_WORDS,
33 ...(baseLanguage ? (LANGUAGE_FILLER_WORDS[baseLanguage] ?? []) : []),
34 ];
35 const cuePattern = correctionCue?.trim().split(/\s+/).map(escapeRegex).join("\\s+");
36 const pattern = new RegExp(
37 `(?<![\\p{L}\\p{N}_])(?:(${cuePattern || "(?!)"})(?![\\p{L}\\p{N}_])|(?:${words.map(escapeRegex).join("|")})(?![\\p{L}\\p{N}_])[,.]?)`,
38 "giu",
39 );
40 const filtered = text.replace(pattern, (_match, cue: string | undefined) => cue ?? "");
41 return filtered
42 .replace(/[^\S\r\n]{2,}/g, " ")
43 .replace(/[^\S\r\n]*([\r\n])[^\S\r\n]*/g, "$1")
44 .trim();
45}