Popularny post ethanak Napisano 3 lutego Popularny post Udostępnij Napisano 3 lutego (edytowany) Cóż - jak zwykle miało być o czymś innym, tylko że ten "czymś" cały czas ma problemy egzystencjonalne typu "a ja nie będę działać po twojemu ale po swojemu". Dlatego dzisiaj o czymś, co powstało w międzyczasie. Dzisiaj przedstawiam bibliotekę poldit, służącą do słownego rozwijania różnych liczb (w tym liczb mianowanych, godzin, dat czy zakresów). Zaczęło się to jakoś 20 lat temu, kiedy potrzebowałem systemu TTS dla Linuxa. Moja Milena działała (i do dziś działa) praktycznie bezproblemowo, tyle że jest przystosowana przede wszystkim do Mbroli, poza tym jest duża, ciężka zbyt specjalizowana (powstała do czytania beletrystyki, więc wiele jej reguł dotyczy właśnie powieści - choćby francuskie rewolucyjne daty, stopnie Reaumura czy wymowa wyrazów obcojęzycznych). Co prawda najnowsze wersje potrafią od biedy wygenerowć tekst dla innych syntezatorów, ale nie jest to jej głównym celem. Dlatego postanowiłem stworzyć coś prostszego, a jednocześnie mającego nieco większe możliwości interpretacji tekstu. Kompletny kod i techniczny opis zamieszczony jest na githubie, nie będę więc się specjalnie powtarzać, postaram się opisać o coś od strony bardziej praktycznej. Jako że główną platformą dla biblioteki jest Linux (w tym RaspiOS) podaję najprostszy sposób instalacji. Zacznę od biblioteki libpoldit i programu poldit wykorzystującego bibliotekę: sudo apt install build-essential git git clone https://github.com/ethanak/poldit.git cd poldit/src make sudo make install Można teraz wypróbować działanie programu, przykładowo: $ poldit napięcie powinno wynosić od 1.5 do 5 V napięcie powinno wynosić od jednego i pięć do pięciu woltów Oczywiście warto wypróbować różne przełączniki, przykładowo: $ poldit -p -n napięcie powinno wynosić od 1.5 do 15 V napięcie powinno wynosić od półtora do pietnastu wolt Jak widać, w tym przypadku poldit zastosował skróconą/potoczną konwersję i skorygował przesadną wymowę. Bieżącą datę i czas można odczytać przykładowo w ten sposób: $ date -Iminutes | cut -f1 -d+ | poldit dwudziesty drugi stycznia dwa tysiące dwadzieścia sześć czternasta pięćdziesiąt siedem Pokazany wyżej sposób będzie działać zarówno na RPi (nawet na maleńkim Zero W), jak i na wszelkich debianobodobnych dystrybucjach Linuksa. Dla innych wystarczy w sposób odpowiedni dla systemu zainstalować pakiety build-essential i git. Oczywiście to co wypisuje poldit nie służy do czytania na konsoli ale do sterowania syntezą mowy. Pomijając rozwiązania wymagające dostępu do internetu, płatne syntezatory (i moją Milenę) dla języka polskiego mamy niewiele praktycznych możliwości: espeak - tu dwie możliwości: albo syntezator Klatta (mechaniczny głos, ale czytelny przy wysokich prędkościach) albo Mbrola (niestety, raczej tylko jako ciekawostka); piper - z założenia głos wysokiej jakości generowany przy użyciu AI. Niestety są to jedynie założenia, bo polskie modele generują coś w stylu mieszanki polsko-rosyjskiej gubiąc po drodze niewygodne spółgłoski; RHVoice - od początku projektowany z myślą o praktycznych zastosowaniach. Po polsku mówi całkiem dobrze, do dyspozycji mamy pięć głosów. Istnieje jeszcze oczywiście korpusowy syntezator mowy (PJWSTK) oraz leciwy Festival, ale to są projekty bardziej akademickie; o ile Festival był w paru aplikacjach używany, o tyle ten polsko-japoński nigdy nie doczekał się praktycznego zastosowania. Aby więc posłuchać efektów trzeba mieć przede wszystkim komputer z wyjściem audio (może być RPi, ale model co najmniej Zero 2 W). Można zacząć od espeaka (jest w repozytoriach) żeby po prostu stwierdzić że to działa. Jednak jeśli ktoś chciałby stworzyć jakąś rzeczywistą aplikację, polecam RHVoice. Dla nowych dystrybucji sprawa jest prosta: $ sudo apt install rhvoice rhvoice-polish Niestety - starsze dystrybucje mogą w repozytoriach mieć wersje bez dostępnego języka polskiego lub w ogóle nie mieć pakietu. W takim przypadku trzeba pobrać go z githuba skompilować samodzielnie. Na szczęście sposób instalacji jest dobrze opisany, wymaga tylko kilku dodatkowych bibliotek z repozytorium (np. libao) i programu scons. Można więc spróbować już czegoś bardziej przydatnego, przykładowo: $ date +'Jest %H:%M, %A, %d.%m.%Y' | poldit -n | RHVoice-test -p natan Taki skrypt można podłączyć np. pod jakiś klawisz i mieć do dyspozycji zegarek, który nie wymaga gapienia się w ekran. Szczególnie w rozwiązaniach bez interfejsu graficznego może to być przydatne. Tak mniej więcej działa czytanie godziny i daty przez różne syntezatory: W załączniku pliki mp3 dla głosów espeak-ng, piper i RHVoice (róœnież z głosem Cezary, który ma tak dziwaczną licencję że nie wiem czy w ogóle można go słuchać i dlatego nie ma go w filmie). Oczywiście czytanie bieżącego roku nie jest w większości przypadków konieczne. Można się bez tego obejść, tyle że trzeba polditowi powiedzieć dokładniej co ma czytać (czyli użyć formatów): $ date +'jest %H:%M, F[DWdM/,:%u %d %m]' | poldit -n | RHVoice-test -p natan W wielu przypadkach programik poldit może być wystarczający, ale na RPi (który jest główną platformą dla której powstała biblioteka) programy pisze się raczej w Pythonie a nie Bashu. Dlatego powstały dwa modułt do Pythona: polditLL - niskopoziomowy moduł, stanowiący wyłącznie interfejs do biblioteki poldit poldit - moduł udostępniający dodatkowe funkcje rozszerzające możliwości polditLL Sposób instalacji jest w miarę prosty: sudo apt install python3-setuptools python3-pip pip3 install build --break-system-packages cd ~/poldit/python/modules python3 -m build --no-isolation pip3 install . --break-system-packages W niektórych dystrybucjach przełącznik --break-system-packages może być niepotrzebny! Oczywiście można moduł zainstalować globalnie, używając sudo w obu przypadkach uruchamiania pip3 Można przetestować moduł np. poleceniem: $ python3 ../demo/kolej.py Wynik powinien wyglądać w ten sposób: Na torze czwartym przy peronie trzecim stoi pociąg do stacji Warszawa Główna. Wagony o numerach jeden do trzy zatrzymują się w sektorze trzecim. Opóźniony o pietnaście minut pociąg intersjity I C dwadzieścia trzy sto czterdzieści cztery "Maruda", ze stacji Warszawa Centralna do stacji Katowice Wschodnie, wjedzie na tor siódmy przy peronie czwartym. No i teraz najważniejsze - co można z tym zrobić? Wyobrażam sobie kilka zastosowań - szczególnie jeśli chodzi o RPi. Jednym z ciekawszych może być modelarstwo kolejowe. RPi Zero 2W obsłuży zarówno jakiś porządny interfejs, ustawi zwrotnice i semafory, będzie sterować lokomotywami i całą infrastrukturą dworca, jak i zasymuluje komumikaty na dworcu. Zamieszczony przykład pokazuje, jak w bardzo prosy sposób można generować komunikaty na dworcu kolejowym czy autobusowym, nie wnikając w prawidłową odmianę (np. "natorze 7 przy peronie 3" czy "autobus odjeżdzą ze stanowiska 7"). Innym typem zastosowań może być automatyka domowa, gdzie czasem infrmacje głosowe są dużo wygodniejsze niż wyświelane na jakimś ekranie, Przykładowo - w mojej domowej instalacji wciśnięcie przycisku na ścianie spowoduje w efekcie przeczytanie na głos aktualnego czasu i temperatury na zewnątrz, albo prognozy pogody na najbliższe dni ściągniętej z openmeteo. Sprawdza się od dłuszego czasu (syntezator RHVoice, głos Alicja), przy czym przycisk jest obok mojego ulubionego fotela Taki programik do prognozy wcale nie jest taki skomplikowany, przykładowe rozwiązanie to: #!/usr/bin/env python3 import requests, os, time, json class weather(object): cachefile = '/dev/shm/prognoza.json' weacodes={ 0: "Czyste niebo", 1: "Przeważnie bezchmurnie", 2: "Zachmurzenie częściowe", 3: "Pochmurno", 45: "Mgła", 48: "Marznąca mgła", 51: "Lekka mżawka", 53: "Umiarkowana mżawka", 55: "Intensywmna mżawka", 56: "Lekka marznąca mżawka", 57: "Intensywna marznąca mżawka", 61: "Niewielki deszcz", 63: "Umiarkowany deszcz", 65: "Intensywny deszcz", 66: "Lekki marznący deszcz", 67: "Silny marznący deszcz", 71: "Słabe opady śniegu", 73: "Uniarkowane opady śniegu", 75: "Silne opady śniegu", 77: "Śnieg ziarnisty", 80: "Przelotne lekkie opady deszczu", 81: "Przelotne umiarkowane opady deszczu", 82: "Przelotne gwałtowne opady deszczu", 85: "Przelotne słabe opady sniegu", 86: "Przelotne intensywne opady sniegu", 95: "Burza z piorunami", 96: "Burza z lekkim gradem", 99: "Burza z silnym gradem" } def __init__(self, latitude, longitude): self.url="https://api.open-meteo.com/v1/forecast?latitude=%.5f&longitude=%.5f&timeformat=iso8601&forecast_days=2&timezone=Europe/Warsaw" + \ "&daily=wind_speed_10m_min,wind_speed_10m_max,weather_code,wind_gusts_10m_max" + \ ",temperature_2m_min,apparent_temperature_min,temperature_2m_max,apparent_temperature_max" + \ ",rain_sum,showers_sum,snowfall_sum" self.url = self.url % (latitude, longitude) self.json = None def getWeatherJSON(self): try: st=os.stat(self.__class__.cachefile) delta = time.time() - st.st_mtime if delta >= 900: raise Exception() return json.loads(open(self.__class__.cachefile,'rb').read()) except: return self.getNetWeather() def getNetWeather(self): try: rc=requests.get(self.url) js=rc.content rc=json.loads(js) open(self.__class__.cachefile,'wb').write(js) return rc except: return None def _getIVals(self, name): return int(self.dly.get(name+'_min')[self.idx]), \ int(self.dly.get(name+'_max')[self.idx]) def _getIVal(self, name): return int(self.dly.get(name)[self.idx]) def getWeatherString(self, tomorrow=False): if self.json is None: self.json = self.getWeatherJSON() if self.json is None: return 'Brak prognozy' self.idx = 1 if tomorrow else 0 self.dly = self.json['daily'] sout = [] wcode = self._getIVal('weather_code') if (wdesc := self.__class__.weacodes.get(wcode)) is not None: sout.append(wdesc) tm1, tm2 = self._getIVals('temperature_2m') to1, to2 = self._getIVals('apparent_temperature') if tm1 == tm2: stm = "temperatura %d °" % tm1 else: stm = "temperatura od %d do %d °" % (tm1, tm2) if to1 == to2: stm += ", odczuwalna %d °" % to1 else: stm += ", odczuwalna %d do %d °" % (to1, to2) sout.append(stm) w1, w2 = self._getIVals('wind_speed_10m') w3 = self._getIVal('wind_gusts_10m_max') if w1 == w2: stm = "wiatr %d km/h" % w1 else: stm = "wiatr %d do %d km/h" % (w1, w2) if w3 > w2: stm += ", w porywach do %d km/h" % w3 sout.append(stm) rain = int(self._getIVal('rain_sum')+self._getIVal('showers_sum')) snow = int(self._getIVal('snowfall_sum')) if rain > 0 or snow > 0: stm = 'przewidywane opady' if rain > 0: stm += ' deszczu %d mm' % rain if snow > 0: stm += ',' if snow > 0: stm += ' śniegu %d cm' % snow sout.append(stm) return '. '.join(sout) latitude = 52.23 longitude = 23.01 w=weather(latitude,longitude) so="Pogoda na dziś: %s. Pogoda na jutro: %s." % ( w.getWeatherString(), w.getWeatherString(True)) import poldit p=poldit.Fonetyzer() print(p.Stringify(so, nat=True)) Należy tylko wpisać do programu właściwe wartości zmiennych latitude i longitude, a wygenerowany tekst wrzucić do syntezatora. No i kolejne zastosowanie, tym razem wymagające posiadania odpowiedniego sprzętu: gadające przystawki do urządzeń pomiarowych. Tu już specjalnie nie mam praktyki (np. mierniki z wyjściem cyfrowym są trochę poza moimi możliwościami finansowymi, ale używam takiego prostego gadającego miernika własnego sumptu). Chyba każdy przyzna, że wtyknięcie końcówek przyrządu w jakąś gęstą płytkę z rastrem rzędu milimetra albo mniej i odwrócenie wzroku żeby popatrzeć na wyświetlacz może skończyć się smętnie. Mogę jednak wyobrazić sobie jakiś interfejs oparty na czymś co ma wejście serial i komunikację WiFi/BT (esp32, RPi Pico czy co kto lubi), czyta dane z miernika wyrzuca np. na smartfona z prostą aplikacją (przykład aplikacji w wątku o suwmiarce) A jeśli chodzi o komunikaty... fajnie by było, aby w pociągach oprócz wyświetlania informacji na wyświetlaczach (do których człowiek albo siedzi tyłem, albo coś/ktoś mu zasłania) były informacje głosowe o bieżących opóźnieniach i przewidywanych czasach przyjazdu na następną stację. Jeśli obsługa może wprowadzić takie dane do wyświetlania - raczej nie byłoby problemu z wygenerowaniem (automatycznym) odpowiedniehgo komunikatu, np. na podstawie rozkładu jazdy i opóźnienia można wygenerować coś w stylu: Następna stacja Trypucie, przyjazd 15:32, opóźnienie 137 min Czytać tego wcale nie musi koniecznie Ivona... Ale chyba za dużo bym od kolei wymagał Oprócz plików mp3 w załączniku źródełko programu do prognozy pogody oraz prosta klasa umożliwiająca użycie RHVoice bezpośrednuio w skrypcie Pythona bez zabawy w (nie zawsze działający) moduł rhvoice-wrapper: poldemo.zip A na koniec konkurs bez nagród: kto wymyśli jakieś sensowne zastosowanie dla poldita? Jak zwykle w razie pytań pozostaję do dyspozycji ethanak Edytowano 3 lutego przez ethanak 6
mkwiatkowski 5 lutego Udostępnij 5 lutego Generalnie bardzo pomocne dla komunikatów, anyway - ta synteza z PJWSTK od Krzysztofa Szklannego to akurat Festival (korpus dla Festivala). Duży plus!
Pomocna odpowiedź
Bądź aktywny - zaloguj się lub utwórz konto!
Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony
Utwórz konto w ~20 sekund!
Zarejestruj nowe konto, to proste!
Zarejestruj się »Zaloguj się
Posiadasz własne konto? Użyj go!
Zaloguj się »