tts-server: normalizza i numeri interi 5+ cifre in parole italiane (Qwen3-TTS li leggeva cifra per cifra)

This commit is contained in:
enne2
2026-08-13 19:54:50 +02:00
parent a8a7716b53
commit 3be42af9bb
2 changed files with 127 additions and 1 deletions
+123
View File
@@ -0,0 +1,123 @@
// text-normalize.h: normalizzazione testo per TTS.
// Qwen3-TTS legge i numeri interi di 5+ cifre cifra per cifra
// ("12345" -> "un, due, tre, quattro, cinque"). Questa funzione li
// converte in parole italiane prima della sintesi.
#pragma once
#include <cstdint>
#include <string>
namespace text_normalize {
static bool is_alnum_utf8(char c) {
unsigned char u = (unsigned char) c;
return (u >= '0' && u <= '9') || (u >= 'A' && u <= 'Z') || (u >= 'a' && u <= 'z') || u >= 0x80;
}
// Converte un intero (0..999.999.999.999) in parole italiane.
static std::string number_to_words(uint64_t n) {
if (n == 0) return "zero";
static const char * units[] = {"", "uno", "due", "tre", "quattro", "cinque", "sei", "sette", "otto", "nove"};
static const char * teens[] = {"dieci", "undici", "dodici", "tredici", "quattordici", "quindici", "sedici",
"diciassette", "diciotto", "diciannove"};
static const char * tens[] = {"", "", "venti", "trenta", "quaranta", "cinquanta", "sessanta", "settanta",
"ottanta", "novanta"};
// Converte un gruppo di 3 cifre (0-999).
auto group3 = [&](int g) -> std::string {
if (g == 0) return "";
std::string out;
int h = g / 100, rest = g % 100;
if (h > 0) {
out += (h == 1) ? "cento" : (std::string(units[h]) + "cento");
}
if (rest >= 10 && rest < 20) {
out += teens[rest - 10];
} else {
int t = rest / 10, u = rest % 10;
if (t > 1) {
std::string dec = tens[t];
if (u == 1 || u == 8) {
dec.pop_back(); // venti+uno=ventuno, venti+otto=ventotto
out += dec + units[u];
} else if (u == 3) {
out += dec + "tr\xc3\xa9"; // ventitré (accento)
} else if (u > 0) {
out += dec + units[u];
} else {
out += dec;
}
} else if (t == 1) {
out += teens[u];
} else if (u > 0) {
out += units[u];
}
}
return out;
};
static const char * scale_singular[] = {"", "mille", "un milione", "un miliardo"};
static const char * scale_plural[] = {"", "mila", "milioni", "miliardi"};
int groups[4] = {0, 0, 0, 0};
uint64_t v = n;
for (int i = 0; i < 4 && v > 0; i++) {
groups[i] = (int) (v % 1000);
v /= 1000;
}
std::string out;
bool first = true;
for (int i = 3; i >= 0; i--) {
int g = groups[i];
if (g == 0) continue;
std::string words = group3(g);
if (i > 0) {
if (g == 1) {
words = scale_singular[i];
} else {
words += " " + std::string(scale_plural[i]);
}
}
if (!first) out += " ";
out += words;
first = false;
}
return out;
}
// Normalizza i numeri interi di 5+ cifre in parole italiane.
// Non tocca: date, orari, decimali, codici, numeri < 5 cifre, numeri > 12 cifre.
static void normalize_numbers(std::string & s) {
std::string out;
out.reserve(s.size() + 32);
size_t i = 0;
const size_t n = s.size();
while (i < n) {
unsigned char c = (unsigned char) s[i];
if (c >= '0' && c <= '9') {
size_t j = i;
while (j < n && s[j] >= '0' && s[j] <= '9') j++;
size_t len = j - i;
bool left_ok = (i == 0) || !is_alnum_utf8(s[i - 1]);
bool right_ok = (j == n) || !is_alnum_utf8(s[j]);
if (len >= 5 && len <= 12 && left_ok && right_ok) {
uint64_t val = 0;
for (size_t k = i; k < j; k++) {
val = val * 10 + (uint64_t) (s[k] - '0');
}
out += number_to_words(val);
} else {
out.append(s, i, len);
}
i = j;
} else {
out += s[i];
i++;
}
}
s.swap(out);
}
} // namespace text_normalize