fonema
Version:
Ultra-minimal Spanish text cleaning library for TTS - 100% Effect-TS & ESM
368 lines (280 loc) โข 13.4 kB
Markdown
Ultra-minimal Spanish text cleaning library for TTS. 100% Effect-TS, ESM-only, zero dependencies. Made by @blissito
```bash
npm install fonema effect
```
Main function that applies all Spanish text cleaning transformations in a single pipeline.
**Returns:** `Effect<string, TextCleaningError>`
- `convertSpanishNumber(num: number): string` - Convert numbers to Spanish words
- `expandSpanishAbbreviation(abbrev: string): string` - Expand Spanish abbreviations
- `convertEmojiToSpanish(emoji: string): string` - Convert emoji to Spanish description
- **Numbers**: `1,234` โ `"mil doscientos treinta y cuatro"`
- **Ordinals**: `1ยบ` โ `"primero"`, `2ยช` โ `"segunda"`
- **Dates**: `15/03/2024` โ `"quince de marzo de dos mil veinticuatro"`
- **Abbreviations**: `Dr.` โ `"Doctor"`, `etc.` โ `"etcรฉtera"`
- **Percentages**: `25%` โ `"veinticinco por ciento"`
- **Emojis**: `๐` โ `"emoji de cara sonriente"`, `๐` โ `"emoji de cohete"`
- **Markdown**: `**bold**` โ `"bold"`, `*italic*` โ `"italic"`, removes headers, lists, links
- **Code blocks**: Removes ```blocks, preserves inline`code`
- **URLs/emails**: Complete removal
- **Punctuation**: RAE-compliant normalization
```typescript
import { cleanTextForTTS } from "fonema";
import { Effect } from "effect";
// Ejemplo que demuestra todas las capacidades de limpieza
const text = `
El Dr. Garcรญa, experto en IA y PLN, ha estado utilizando fonema v1.2.3 en su investigaciรณn durante 5 aรฑos.
Naciรณ el 15/03/1980 y atiende en C/ Mayor, 123, 2ยบB.
Contacto: dr.garcia@clinica.com | https://drgarcia.es
Su รบltimo estudio muestra una mejora del 75% en la naturalidad del TTS usando la normalizaciรณn de texto de fonema:
\`\`\`typescript
// Antes: "El Dr. Garcรญa atiende en C/ Mayor, 123"
// Despuรฉs de fonema: "El Doctor Garcรญa atiende en Calle Mayor, ciento veintitrรฉs"
const texto = "El Dr. Garcรญa atiende en C/ Mayor, 123";
const textoLimpio = await Effect.runPromise(cleanTextForTTS(texto));
\`\`\`
ยกAgenda tu cita al 555-123-4567!`;
const programa = cleanTextForTTS(text);
Effect.runSync(programa);
/* โ
"El Doctor Garcรญa, experto en I A y P L N, ha estado utilizando fonema v uno punto dos punto tres en su investigaciรณn durante cinco aรฑos.
Naciรณ el quince de marzo de mil novecientos ochenta y atiende en Calle Mayor, ciento veintitrรฉs, segundo B.
Contacto:
Su รบltimo estudio muestra una mejora del setenta y cinco por ciento en la naturalidad del T T S usando la normalizaciรณn de texto de fonema:
ยกAgenda tu cita al cinco cinco cinco, uno veintitrรฉs, cuarenta y cinco, sesenta y siete!"
*/
```
1. **Abreviaturas**: `Dr.` โ `Doctor`
2. **Fechas**: `15/03/1980` โ `quince de marzo de mil novecientos ochenta`
3. **Direcciones**: `C/ Mayor` โ `Calle Mayor`
4. **Nรบmeros**:
- `123` โ `ciento veintitrรฉs`
- `1.2.3` โ `uno punto dos punto tres`
- `5` โ `cinco`
5. **Porcentajes**: `75%` โ `setenta y cinco por ciento`
6. **Emojis**: `๐` โ `emoji de cohete`, `๐` โ `emoji de cara sonriente`
7. **Eliminรณ**:
- Email: `dr.garcia@clinica.com`
- URL: `https://drgarcia.es`
- Bloque de cรณdigo completo
8. **Nรบmeros de telรฉfono**: `555-123-4567` โ `cinco cinco cinco, uno veintitrรฉs, cuarenta y cinco, sesenta y siete`
9. **Formato de piso**: `2ยบB` โ `segundo B`
10. **Versiones**: `v1.2.3` โ `v uno punto dos punto tres`
11. **Abreviaturas tรฉcnicas**: `IA` โ `I A`, `PLN` โ `P L N`, `TTS` โ `T T S`
```typescript
import {
cleanTextForTTS,
convertSpanishNumber,
convertEmojiToSpanish,
} from "fonema";
import { Effect } from "effect";
// Number conversion
convertSpanishNumber(1234); // โ "mil doscientos treinta y cuatro"
// Emoji conversion
convertEmojiToSpanish("๐"); // โ "emoji de cohete"
convertEmojiToSpanish("๐"); // โ "emoji de cara sonriente"
convertEmojiToSpanish("โค๏ธ"); // โ "emoji de corazรณn rojo"
// Full text cleaning
const program = Effect.gen(function* () {
const result = yield* cleanTextForTTS(
"El 15/03/2024 el Dr. Smith presentรณ el 50% del proyecto. ยกFue increรญble! ๐๐"
);
console.log(result);
// โ "El quince de marzo de dos mil veinticuatro el Doctor Smith presentรณ el cincuenta por ciento del proyecto. ยกFue increรญble! emoji de cohete emoji de cara sonriente"
});
Effect.runSync(program);
```
```typescript
import { cleanTextForTTS } from "fonema";
import { Effect } from "effect";
const program = cleanTextForTTS("Some text").pipe(
Effect.catchAll((error) => Effect.succeed(`Fallback: ${error.message}`))
);
Effect.runSync(program);
```
```typescript
import { cleanTextForTTS } from "fonema";
import { Effect } from "effect";
import { TextToSpeechClient } from "@google-cloud/text-to-speech";
const client = new TextToSpeechClient();
const synthesizeSpanishText = (text: string) =>
Effect.gen(function* () {
// Clean text with fonema
const cleanedText = yield* cleanTextForTTS(text);
// Google TTS request
const [response] = yield* Effect.tryPromise(() =>
client.synthesizeSpeech({
input: { text: cleanedText },
voice: {
languageCode: "es-ES",
name: "es-ES-Neural2-A",
},
audioConfig: { audioEncoding: "MP3" },
})
);
return response.audioContent;
});
// Usage
const program = synthesizeSpanishText(
"El Dr. Garcรญa tiene 25 aรฑos y naciรณ el 15/03/1998."
);
Effect.runPromise(program).then((audioBuffer) => {
// Handle audio buffer
});
```
```typescript
import { cleanTextForTTS } from "fonema";
import { Effect } from "effect";
const elevenLabsTTS = (text: string, voiceId: string) =>
Effect.gen(function* () {
const cleanedText = yield* cleanTextForTTS(text);
const response = yield* Effect.tryPromise(() =>
fetch(`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`, {
method: "POST",
headers: {
Accept: "audio/mpeg",
"Content-Type": "application/json",
"xi-api-key": process.env.ELEVENLABS_API_KEY!,
},
body: JSON.stringify({
text: cleanedText,
model_id: "eleven_multilingual_v2",
voice_settings: {
stability: 0.5,
similarity_boost: 0.5,
},
}),
})
);
return yield* Effect.tryPromise(() => response.arrayBuffer());
});
```
```typescript
import { cleanTextForTTS } from "fonema";
import { Effect } from "effect";
import OpenAI from "openai";
const openai = new OpenAI();
const openAITTS = (text: string) =>
Effect.gen(function* () {
const cleanedText = yield* cleanTextForTTS(text);
const mp3 = yield* Effect.tryPromise(() =>
openai.audio.speech.create({
model: "tts-1",
voice: "nova",
input: cleanedText,
})
);
return yield* Effect.tryPromise(() => mp3.arrayBuffer());
});
```
```typescript
import { cleanTextForTTS } from "fonema";
import { Effect } from "effect";
interface TTSService {
synthesize(text: string, options: any): Promise<ArrayBuffer>;
}
const createTTSPipeline =
(service: TTSService) => (text: string, options: any) =>
Effect.gen(function* () {
// Always clean Spanish text first
const cleanedText = yield* cleanTextForTTS(text);
// Then pass to any TTS service
return yield* Effect.tryPromise(() =>
service.synthesize(cleanedText, options)
);
});
// Usage with any TTS service
const myTTSPipeline = createTTSPipeline(myTTSService);
const program = myTTSPipeline("Text with nรบmeros 123 and Dr. abbreviations", {
voice: "spanish-voice",
speed: 1.0,
});
```
fonema limpia automรกticamente el formato markdown preservando el contenido:
```typescript
import { cleanTextForTTS } from "fonema";
import { Effect } from "effect";
const markdownText = `
Este texto tiene **negritas** y *cursivas*.
Tambiรฉn __negritas__ y _cursivas_ con guiones bajos.
Y texto ~~tachado~~ que se limpia.
- Lista con viรฑetas
- Segundo elemento
1. Lista numerada
2. Segundo elemento
> Cita en blockquote
[](https://example.com)

Cรณdigo \`inline\` se preserva.
`;
const programa = cleanTextForTTS(markdownText);
Effect.runSync(programa);
/* Resultado:
"Tรญtulo Principal Subtรญtulo Este texto tiene negritas y cursivas. Tambiรฉn negritas y cursivas con guiones bajos. Y texto tachado que se limpia. Lista con viรฑetas Segundo elemento Lista numerada Segundo elemento Cita en blockquote Enlace a sitio web Cรณdigo inline se preserva."
*/
```
- โ
**Negritas**: `**texto**` y `__texto__` โ `texto`
- โ
**Cursivas**: `*texto*` y `_texto_` โ `texto`
- โ
**Tachado**: `~~texto~~` โ `texto`
- โ
**Tรญtulos**: `
- โ
**Listas**: `- item` y `1. item` โ `item`
- โ
**Blockquotes**: `> cita` โ `cita`
- โ
**Enlaces**: `[texto](url)` โ `texto`
- โ
**Imรกgenes**: `` โ (se eliminan)
- โ
**Cรณdigo inline**: `` `cรณdigo` `` โ `cรณdigo`
- โ
**Cรณdigo en bloque**: ` ```cรณdigo``` ` โ (se elimina)
- โ
**HTML tags**: `<tag>contenido</tag>` โ `contenido`
fonema convierte automรกticamente emojis a descripciones en espaรฑol natural para TTS:
```typescript
import { cleanTextForTTS, convertEmojiToSpanish } from "fonema";
import { Effect } from "effect";
// Conversiรณn individual de emojis
convertEmojiToSpanish("๐"); // โ "emoji de cohete"
convertEmojiToSpanish("๐"); // โ "emoji de cara sonriente"
convertEmojiToSpanish("โค๏ธ"); // โ "emoji de corazรณn rojo"
convertEmojiToSpanish("๐"); // โ "emoji de fiesta"
// Texto con mรบltiples emojis
const textoConEmojis =
"ยกHola! ๐ Me encanta programar ๐ y usar React โค๏ธ para crear apps increรญbles ๐";
const programa = cleanTextForTTS(textoConEmojis);
Effect.runSync(programa);
/* Resultado:
"ยกHola! emoji de cara sonriente Me encanta programar emoji de cohete y usar React emoji de corazรณn rojo para crear apps increรญbles emoji de fiesta"
*/
```
fonema incluye mรกs de 400 emojis comunes con descripciones en espaรฑol:
- **Caras y emociones**: ๐ ๐ ๐ ๐ ๐ ๐
๐คฃ ๐ ๐ ๐ ๐ ๐ ๐ ๐ฅฐ ๐ ๐คฉ ๐ ๐ โบ๏ธ ๐ ๐ ๐ฅฒ ๐ ๐ ๐ ๐คช ๐ ๐ค ๐ค ๐คญ ๐คซ ๐ค ๐ค ๐คจ ๐ ๐ ๐ถ ๐ ๐ ๐ ๐ฌ ๐คฅ ๐ ๐ ๐ โน๏ธ ๐ฃ ๐ ๐ซ ๐ฉ ๐ฅบ ๐ข ๐ญ ๐ค ๐ ๐ก ๐คฌ ๐คฏ ๐ณ ๐ฅต ๐ฅถ ๐ฑ ๐จ ๐ฐ ๐ฅ ๐ ๐ค ๐ค ๐ด ๐ค ๐ช ๐ต ๐ค ๐ฅด ๐คข ๐คฎ ๐คง ๐ท ๐ค ๐ค
- **Corazones**: โค๏ธ ๐งก ๐ ๐ ๐ ๐ ๐ค ๐ค ๐ค ๐ โฃ๏ธ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐
- **Gestos y manos**: ๐ ๐ ๐ โ๏ธ ๐ค ๐ค ๐ค ๐ค ๐ ๐ ๐ ๐ โ๏ธ โ ๐ค ๐๏ธ ๐ ๐ ๐ค ๐ โ๏ธ ๐ ๐ ๐ ๐คฒ ๐ค ๐ค โ ๐ ๐ซถ
- **Objetos y sรญmbolos**: ๐ฅ ๐ฏ ๐ซ โญ ๐ โจ โก ๐ฅ ๐ข ๐จ ๐ฆ ๐ง ๐ โ๏ธ โ
โ๏ธ ๐ง๏ธ โ๏ธ ๐ฉ๏ธ โ๏ธ โ๏ธ โ ๐ช๏ธ ๐
- **Comida y bebidas**: ๐ ๐ ๐ ๐ ๐ ๐ ๐ฅ ๐
๐ฅ ๐ฝ ๐ฅ ๐ ๐ง ๐ฅ ๐ ๐ ๐ ๐ ๐ญ ๐ฅช ๐ฎ ๐ฏ ๐ ๐ ๐ ๐ ๐ค ๐ฃ ๐ฆ ๐ฐ ๐ ๐ช ๐ซ ๐ฌ ๐ญ โ ๐ต ๐ฅค ๐บ ๐ท ๐ฅ ๐พ
- **Animales**: ๐ถ ๐ฑ ๐ญ ๐น ๐ฐ ๐ฆ ๐ป ๐ผ ๐จ ๐ฏ ๐ฆ ๐ฎ ๐ท ๐ธ ๐ต ๐ ๐ ๐ ๐ ๐ ๐ง ๐ฆ ๐ค ๐ฃ ๐ฅ ๐ฆ ๐ฆ
๐ฆ ๐ฆ ๐บ ๐ ๐ด ๐ฆ ๐ ๐ ๐ฆ ๐ ๐ ๐ ๐ฆ ๐ท๏ธ ๐ฆ ๐ข ๐ ๐ฆ ๐ ๐ฆ ๐ฆ ๐ฆ ๐ก ๐ ๐ ๐ฌ ๐ณ ๐ ๐ฆ
- **Actividades y deportes**: โฝ ๐ ๐ โพ ๐ฅ ๐พ ๐ ๐ ๐ฅ ๐ฑ ๐ช ๐ ๐ธ ๐ฅ
โณ ๐ช ๐น ๐ฃ ๐คฟ ๐ฅ ๐ฅ ๐ฝ ๐น ๐ท โธ๏ธ ๐ฅ ๐ฟ โท๏ธ ๐ ๐ช ๐๏ธ ๐คธ ๐คผ ๐คฝ ๐คพ ๐คน ๐ง ๐ ๐
- **Viajes y lugares**: ๐ ๐ ๐ ๐ ๐ ๐๏ธ ๐ ๐ ๐ ๐ ๐ป ๐ ๐ ๐ ๐๏ธ ๐ต ๐ฒ ๐ด ๐ โ๏ธ ๐ฉ๏ธ ๐ ๐ธ ๐ข โต ๐ค โด๏ธ ๐ฅ๏ธ ๐ ๐ ๐ ๐
๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ก ๐ฐ๏ธ
- **Objetos y herramientas**: ๐ฑ ๐ป ๐ฅ๏ธ โจ๏ธ ๐ฑ๏ธ ๐ฒ๏ธ ๐ฝ ๐พ ๐ฟ ๐ ๐งฎ ๐ฅ ๐น ๐ท ๐ธ ๐ผ ๐ ๐ ๐ฏ๏ธ ๐ก ๐ฆ ๐ฎ ๐ช ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ฐ ๐๏ธ ๐ ๐ ๐ท๏ธ ๐ฐ ๐ช ๐ด ๐ต ๐ถ ๐ท ๐ธ ๐ณ ๐งพ ๐ โ๏ธ ๐ช ๐งฐ ๐ง ๐จ โ๏ธ ๐ ๏ธ โ๏ธ ๐ช ๐ช ๐ฉ โ๏ธ ๐ชค ๐งฒ ๐ชฃ ๐งฝ ๐งด ๐งท ๐งน ๐งบ ๐ช ๐ช ๐ช ๐๏ธ ๐๏ธ ๐ฟ ๐ ๐ฝ ๐ช ๐งป ๐ชฅ ๐งผ ๐ช ๐งฏ ๐
- **Mรบsica y entretenimiento**: ๐ต ๐ถ ๐ผ ๐น ๐ฅ ๐ท ๐บ ๐ธ ๐ช ๐ป ๐ค ๐ง ๐ป ๐ฌ ๐ญ ๐ช ๐จ ๐ฏ ๐ฒ ๐ฎ ๐น๏ธ ๐ฐ ๐ณ
- **Magia y fantasรญa**: ๐ช ๐ฎ ๐งฟ ๐ชฌ ๐ ๐ป ๐ โ ๏ธ ๐ฝ ๐พ ๐ค ๐
๐คถ ๐ง ๐ง ๐ง ๐ง ๐ง ๐ง ๐ง ๐ฆธ ๐ฆน
Los emojis no reconocidos se convierten automรกticamente en "emoji" genรฉrico.
MIT [Fixtergeek.com](https://www.fixtergeek.com)