Google TTS - odczyt czasu z polską deklinacją

Odczyt głosowy danych liczbowych z użyciem Google TTS jest dla języka polskiego sporym wyzwaniem, ponieważ TTS nie wie, w jakim przypadku deklinacji podać wynik. Nie jest to AI, więc z kontekstu zdania nic nie może wykminić.
Dotychczasowe komunikaty godzinowe brzmiały dla mnie lekko satyrycznie, więc napisałem funkcję, która podaje godzinę w dowolnie wybranym przypadku występującym w języku polskim. Dzielę się nią z Wami, bo może macie podobny problem z poziomem akceptacji poprawności językowej Google TTS.

Na wejściu funkcji wymagane są dwa parametry:

msg.datownik - //dowolny łańcuch w formacie daty, np. timestamp
msg.przypadek - //łańcuch tekstowy wskazujący wybrany przypadek deklinacji. Możliwe wartości tego łańcucha: mianownik, dopelniacz, celownik, biernik, narzednik, miejscownik.

Na wyjściu otrzymujemy tekst z datą i godziną odczytaną przez dowolny media_player we wskazanym przypadku polskiej deklinacji.

Funkcja wygląda tak:

//# Dane WEJŚCIOWE #// 
const teraz = new Date(msg.datownik);
const przypadek = msg.przypadek;

//# Tablice językowe #//
const dni_tygodnia_mianownik = ["niedziela", "poniedziałek", "wtorek", "środa", "czwartek", "piątek", "sobota"];
const dni_tygodnia_dopelniacz = ["niedzieli", "poniedziałku", "wtorku", "środy", "czwartku", "piątku", "soboty"];
const dni_tygodnia_celownik = ["niedzieli", "poniedziałkowi", "wtorkowi", "środzie", "czwartkowi", "piątkowi", "sobocie"];
const dni_tygodnia_biernik = ["niedzielę", "poniedziałek", "wtorek", "środę", "czwartek", "piątek", "sobotę"];
const dni_tygodnia_narzednik = ["niedzielą", "poniedziałkiem", "wtorkiem", "środą", "czwartkiem", "piątkiem", "sobotą"];
const dni_tygodnia_miejscownik = ["niedzieli", "poniedziałku", "wtorku", "środzie", "czwartku", "piątku", "sobocie"];

const miesiace = ["stycznia", "lutego", "marca", "kwietnia", "maja", "czerwca", "lipca",
    "sierpnia", "września", "października", "listopada", "grudnia"];

const godziny_mianownik = ["zero", "pierwsza", "druga", "trzecia", "czwarta", "piąta",
    "szósta", "siódma", "ósma", "dziewiąta", "dziesiąta", "jedenasta",
    "dwunasta", "trzynasta", "czternasta", "piętnasta", "szesnasta",
    "siedemnasta", "osiemnasta", "dziewiętnasta", "dwudziesta",
    "dwudziesta pierwsza", "dwudziesta druga", "dwudziesta trzecia"];
const godziny_dopelniacz = ["zero", "pierwszej", "drugiej", "trzeciej", "czwartej", "piątej",
    "szóstej", "siódmej", "ósmej", "dziewiątej", "dziesiątej", "jedenastej",
    "dwunastej", "trzynastej", "czternastej", "piętnastej", "szesnastej",
    "siedemnastej", "osiemnastej", "dziewiętnastej", "dwudziestej",
    "dwudziestejpierwszej", "dwudziestejdrugiej", "dwudziestejtrzeciej"];
const godziny_celownik = ["zero", "pierwszej", "drugiej", "trzeciej", "czwartej", "piątej",
    "szóstej", "siódmej", "ósmej", "dziewiątej", "dziesiątej", "jedenastej",
    "dwunastej", "trzynastej", "czternastej", "piętnastej", "szesnastej",
    "siedemnastej", "osiemnastej", "dziewiętnastej", "dwudziestej",
    "dwudziestejpierwszej", "dwudziestejdrugiej", "dwudziestejtrzeciej"];
const godziny_biernik = ["zero", "pierwszą", "drugą", "trzecią", "czwartą", "piątą",
    "szóstą", "siódmą", "ósmą", "dziewiątą", "dziesiątą", "jedenastą",
    "dwunastą", "trzynastą", "czternastą", "piętnastą", "szesnastą",
    "siedemnastą", "osiemnastą", "dziewiętnastą", "dwudziestą",
    "dwudziestąpierwszą", "dwudziestądrugią", "dwudziestątrzecią"];
const godziny_narzednik = ["zero", "pierwszą", "drugą", "trzecią", "czwartą", "piątą",
    "szóstą", "siódmą", "ósmą", "dziewiątą", "dziesiątą", "jedenastą",
    "dwunastą", "trzynastą", "czternastą", "piętnastą", "szesnastą",
    "siedemnastą", "osiemnastą", "dziewiętnastą", "dwudziestą",
    "dwudziestąpierwszą", "dwudziestądrugią", "dwudziestątrzecią"];
const godziny_miejscownik = ["zero", "pierwszej", "drugiej", "trzeciej", "czwartej", "piątej",
    "szóstej", "siódmej", "ósmej", "dziewiątej", "dziesiątej", "jedenastej",
    "dwunastej", "trzynastej", "czternastej", "piętnastej", "szesnastej",
    "siedemnastej", "osiemnastej", "dziewiętnastej", "dwudziestej",
    "dwudziestejpierwszej", "dwudziestejdrugiej", "dwudziestejtrzeciej"];

const dzien_slowo = ["zerowy", "pierwszy", "drugi", "trzeci", "czwarty", "piąty", "szósty",
    "siódmy", "ósmy", "dziewiąty", "dziesiąty", "jedynasty",
    "dwunasty", "trzynasty", "czternasty", "piętnasty", "szesnasty",
    "siedemnasty", "osiemnasty", "dziewiętnasty", "dwudziesty",
    "dwudziestypierwszy", "dwudziestydrugi", "dwudziestytrzeci",
    "dwudziestyczwarty", "dwudziestypiąty", "dwudziestyszósty",
    "dwudziestysiódmy", "dwudziestyósmy", "dwudziestydziewiąty",
    "trzydziesty", "trzydziestypierwszy"];

switch (przypadek)
    {
    case 'mianownik':
        akt_godzina = `${godziny_mianownik[teraz.getHours()]}`;
        dzien_tygodnia = `${dni_tygodnia_mianownik[teraz.getDay()]}`;
        break;
    case 'dopelniacz':
        akt_godzina = `${godziny_dopelniacz[teraz.getHours()]}`;
        dzien_tygodnia = `${dni_tygodnia_dopelniacz[teraz.getDay()]}`;
        break;
    case 'celownik':
        akt_godzina = `${godziny_celownik[teraz.getHours()]}`;
        dzien_tygodnia = `${dni_tygodnia_celownik[teraz.getDay()]}`;
        break;
    case 'biernik':
        akt_godzina = `${godziny_biernik[teraz.getHours()]}`;
        dzien_tygodnia = `${dni_tygodnia_biernik[teraz.getDay()]}`;
        break;
    case 'narzednik':
        akt_godzina = `${godziny_narzednik[teraz.getHours()]}`;
        dzien_tygodnia = `${dni_tygodnia_narzednik[teraz.getDay()]}`;
        break;
    case 'miejscownik':
        akt_godzina = `${godziny_miejscownik[teraz.getHours()]}`;
        dzien_tygodnia = `${dni_tygodnia_miejscownik[teraz.getDay()]}`;
        break;
    default:
        break;
    }

//# Dane WYJŚCIOWE #//
msg.dzien_tygodnia = dzien_tygodnia;
msg.dzien_miesiaca = `${dzien_slowo[teraz.getDate()]}`;
msg.miesiac = `${miesiace[teraz.getMonth()]}`;
msg.rok = teraz.getFullYear();
msg.godzina = akt_godzina;
msg.minuta = teraz.getMinutes();

return msg;

Użyta tu instrukcja warunkowa switch wybiera ze zmiennych tablicowych właściwie odmienione słowo dla wskazanej w msg.datownik godziny, w przypadku deklinacji wskazanym w msg.przypadek.
Odmianie nie podlega dzień i nazwa miesiąca oraz rok i minuty, ponieważ wartości te odczytywane są przez Google TTS poprawnie.
Mozna teraz budować dowolne sekwencje zdaniowe z użyciem czasu i daty w wybranym przypadku deklinacji, jaki chcemy użyć.

Przykład zastosowania:

Nod Zegar słownie, zawierający powyższą funkcję zamienia wartość zmiennej typu timestamp czasu zakończenia pracy zmywarki na tekst, który następnie doklejany jest do zdania powstającego w następnym nodzie (koniec zadania) i co godzinę taki komunikat idzie na stojący w kuchni media_player.
Czyż nie fajne i praktyczne rozwiązanie?

W niektórych momentach odczytywanych przez TTS łańcuchów tekstowych przydałyby się dodatkowe 1- lub 2-sekundowe pauzy, żeby mówiony tekst brzmiał bardziej “po ludzku”. Ale nie wiem, czy istnieją jakieś specjalne znaki “nieme”, które - umieszczone w łańcuchu dałyby pożądany efekt. Może ktoś z Was wie coś na ten temat?

4 polubienia

A co to za nod gdzie masz tą funkcję? Bo taki z zegarkiem to u mnie time, ale tam funkcji nie da sie wpisać, a funkcyjny inaczej wyglada.

To jest nod typu subflow, bo wykorzystuję go w wielu miejscach. Wygląda tak:

image

…i zawiera funkcję, którą widzisz powyżej.

A dlaczego subflow, a nie zwykły nod funkcyjny? Bo zamierzam dalej go rozbudowywac i nie chce mi się robić tych samych zmian wszędzie tam, gdzie go używam… :wink:

AAAaaa… nie pomyślałem. Ikonka zegarka mnie trochę kierowała, że to jakiś nod funkcyjny :stuck_out_tongue:

U mnie to nie chce działać - możesz wrzucić taki przykładowy blueprint z całym flow? :slight_smile:

Trudno powiedzieć, co i dlaczego u Ciebie nie działa, przyczyn może być wiele. I co to znaczy, że “nie działa”…głośnik milczy? funkcja zgłasza jakiś błąd (jaki?)? błąd w nazwach zmiennych?..

Załączam przykładowy flow dla komunikatów z piekarnika:

piekarnik.json (9,9 KB)

Z chwilą ustawienia temperatury piekarnika sprawdzany jest czas zakończenia jego nagrzewania. Czas ten w formacie timestamp przekazywany jest do funkcji [Zegar Słownie] w zmiennej msg.datownik. W tym samym nodzie zmienna msg.przypadek ustawiana jest na dopelniacz i całość wpada do funkcji słownego odczytu czasu w określonym przypadku deklinacji (tutaj: dopełniacz). Wynik funkcji (czyli godzina tekstowa w dopełniaczu) jest następnie uzupełniany komentarzem i przetwarzany w kolejnej funkcji [wiad 1] na określony format wiadomości, która z kolei leci na wybrany głośnik wskazany w zmiennej msg.wiadomosc.odbiorca.