tts-server: normalizza i numeri interi 5+ cifre in parole italiane (Qwen3-TTS li leggeva cifra per cifra)
This commit is contained in:
@@ -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
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "qwen.h"
|
#include "qwen.h"
|
||||||
#include "rvq-file.h"
|
#include "rvq-file.h"
|
||||||
|
#include "text-normalize.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@@ -222,7 +223,9 @@ int main(int argc, char ** argv) {
|
|||||||
be.synthesize = [q, &lang](const tts_request & req, const tts_sink & sink, std::string & err) -> int {
|
be.synthesize = [q, &lang](const tts_request & req, const tts_sink & sink, std::string & err) -> int {
|
||||||
struct qt_tts_params p;
|
struct qt_tts_params p;
|
||||||
qt_tts_default_params(&p);
|
qt_tts_default_params(&p);
|
||||||
p.text = req.input.c_str();
|
std::string normalized = req.input;
|
||||||
|
text_normalize::normalize_numbers(normalized);
|
||||||
|
p.text = normalized.c_str();
|
||||||
p.lang = lang.c_str();
|
p.lang = lang.c_str();
|
||||||
|
|
||||||
// Copy the registered voice latents out under the lock: the
|
// Copy the registered voice latents out under the lock: the
|
||||||
|
|||||||
Reference in New Issue
Block a user