Come creare un programma di sintesi vocale

Immagine di Maria Salabaieva da unsplash

È passato esattamente un decennio da quando ho iniziato a partecipare al GeekCon (sì, una conferenza di geek 🙂) — un hackathon-makeathon della durata di un fine settimana in cui tutti i progetti devono essere inutili e solo per divertimento, e quest’anno c’è stata una svolta entusiasmante: tutti i progetti dovevano incorporare una qualche forma di intelligenza artificiale.

Il progetto del mio gruppo era un gioco di sintesi vocale, ed ecco come funziona: l’utente seleziona un personaggio con cui parlare e poi esprime verbalmente tutto ciò che desidera al personaggio. Questo input parlato viene trascritto e inviato a ChatGPT, che risponde come se fosse il personaggio. La risposta viene quindi letta ad alta voce utilizzando la tecnologia di sintesi vocale.

Ora che il gioco è attivo e funzionante, portando risate e divertimento, ho creato questa guida pratica per aiutarti a creare un gioco simile da solo. Nel corso dell’articolo esploreremo anche le varie considerazioni e decisioni che abbiamo preso durante l’hackathon.

Vuoi vedere il codice completo? Ecco il link!

Una volta che il server è in esecuzione, l’utente sentirà l’app “parlare”, chiedendogli di scegliere la figura con cui desidera parlare e iniziare a conversare con il personaggio selezionato. Ogni volta che vogliono parlare ad alta voce, devono tenere premuto un tasto sulla tastiera mentre parlano. Quando finiranno di parlare (e rilasceranno il tasto), la loro registrazione verrà trascritta da Whisper (un modello di sintesi vocale di OpenAI), e la trascrizione verrà inviata a ChatGPT per una risposta. La risposta verrà letta ad alta voce utilizzando una libreria di sintesi vocale e l’utente la ascolterà.

Disclaimer

Nota: il progetto è stato sviluppato su sistema operativo Windows e incorpora il pyttsx3 libreria, che non è compatibile con i chip M1/M2. COME pyttsx3 non è supportato su Mac, si consiglia agli utenti di esplorare librerie di sintesi vocale alternative compatibili con gli ambienti macOS.

Integrazione Openai

Ne ho utilizzati due OpenAI Modelli: Whisperper la trascrizione da parlato a testo e il ChatGPT API per generare risposte in base all’input dell’utente sulla figura selezionata. Anche se farlo costa denaro, il modello di prezzo è molto economico e, personalmente, la mia fattura è ancora inferiore a $ 1 per tutto il mio utilizzo. Per iniziare, ho effettuato un deposito iniziale di $ 5 e, ad oggi, non ho esaurito questo deposito e non scadrà prima di un anno.
Non ricevo alcun pagamento o beneficio da OpenAI per aver scritto questo.

Una volta ottenuto il tuo OpenAI Chiave API: impostala come variabile di ambiente da utilizzare quando si effettuano le chiamate API. Assicurati di non inserire la tua chiave nel codebase o in qualsiasi luogo pubblico e di non condividerla in modo non sicuro.

Sintesi vocale in testo: crea trascrizione

L’implementazione della funzionalità di sintesi vocale è stata ottenuta utilizzando WhisperUN OpenAI modello.

Di seguito è riportato lo snippet di codice per la funzione responsabile della trascrizione:

async def get_transcript(audio_file_path: str, 
text_to_draw_while_waiting: str) -> Optional(str):
openai.api_key = os.environ.get("OPENAI_API_KEY")
audio_file = open(audio_file_path, "rb")
transcript = None

async def transcribe_audio() -> None:
nonlocal transcript
try:
response = openai.Audio.transcribe(
model="whisper-1", file=audio_file, language="en")
transcript = response.get("text")
except Exception as e:
print(e)

draw_thread = Thread(target=print_text_while_waiting_for_transcription(
text_to_draw_while_waiting))
draw_thread.start()

transcription_task = asyncio.create_task(transcribe_audio())
await transcription_task

if transcript is None:
print("Transcription not available within the specified timeout.")

return transcript

Questa funzione è contrassegnata come asincrona (asincrona) poiché la chiamata API potrebbe impiegare del tempo per restituire una risposta e noi l’attendiamo per garantire che il programma non proceda finché non viene ricevuta la risposta.

Come puoi vedere, il get_transcript la funzione richiama anche il file print_text_while_waiting_for_transcription funzione. Perché? Poiché ottenere la trascrizione è un compito che richiede molto tempo, volevamo tenere l’utente informato che il programma stava elaborando attivamente la sua richiesta e non si bloccava o non rispondeva. Di conseguenza, questo testo viene gradualmente stampato mentre l’utente attende il passaggio successivo.

Corrispondenza di stringhe utilizzando FuzzyWuzzy per il confronto del testo

Dopo aver trascritto il discorso in testo, lo abbiamo utilizzato così com’è oppure abbiamo tentato di confrontarlo con una stringa esistente.

I casi d’uso di confronto erano: selezionare una figura da un elenco predefinito di opzioni, decidere se continuare a giocare o meno e, quando si sceglie di continuare, decidere se scegliere una nuova figura o restare con quella attuale.

In questi casi, volevamo confrontare la trascrizione dell’input parlato dell’utente con le opzioni nei nostri elenchi, quindi abbiamo deciso di utilizzare il comando FuzzyWuzzy libreria per la corrispondenza delle stringhe.

Ciò ha consentito di scegliere l’opzione più vicina dall’elenco, purché il punteggio corrispondente superasse una soglia predefinita.

Ecco uno snippet della nostra funzione:

def detect_chosen_option_from_transcript(
transcript: str, options: List(str)) -> str:
best_match_score = 0
best_match = ""

for option in options:
score = fuzz.token_set_ratio(transcript.lower(), option.lower())
if score > best_match_score:
best_match_score = score
best_match = option

if best_match_score >= 70:
return best_match
else:
return ""

Se vuoi saperne di più su FuzzyWuzzy libreria e le sue funzioni: puoi consultare un articolo che ho scritto a riguardo Qui.

Ottieni risposta ChatGPT

Una volta ottenuta la trascrizione, possiamo inviarla a ChatGPT per ottenere una risposta.

Per ciascuno ChatGPT richiesta, abbiamo aggiunto un prompt che chiede una risposta breve e divertente. Lo abbiamo anche detto ChatGPT quale figura fingere di essere.

Quindi la nostra funzione era la seguente:

def get_gpt_response(transcript: str, chosen_figure: str) -> str:
system_instructions = get_system_instructions(chosen_figure)
try:
return make_openai_request(
system_instructions=system_instructions,
user_question=transcript).choices(0).message("content")
except Exception as e:
logging.error(f"could not get ChatGPT response. error: {str(e)}")
raise e

e le istruzioni del sistema erano le seguenti:

def get_system_instructions(figure: str) -> str:
return f"You provide funny and short answers. You are: {figure}"

Sintesi vocale da testo

Per la parte di sintesi vocale abbiamo optato per una libreria Python chiamata pyttsx3. Questa scelta non solo è stata semplice da implementare ma ha anche offerto numerosi vantaggi aggiuntivi. È gratuito, fornisce due opzioni vocali – maschile e femminile – e ti consente di selezionare la velocità di pronuncia in parole al minuto (velocità del parlato).

Quando un utente avvia il gioco, sceglie un personaggio da un elenco predefinito di opzioni. Se non riuscissimo a trovare una corrispondenza per ciò che hanno detto nel nostro elenco, selezioneremo casualmente un personaggio dal nostro elenco di “figure di riserva”. In entrambi gli elenchi, ogni personaggio era associato a un genere, quindi la nostra funzione di sintesi vocale ha ricevuto anche l’ID vocale corrispondente al genere selezionato.

Ecco come appariva la nostra funzione di sintesi vocale:

def text_to_speech(text: str, gender: str = Gender.FEMALE.value) -> None:
engine = pyttsx3.init()

engine.setProperty("rate", WORDS_PER_MINUTE_RATE)
voices = engine.getProperty("voices")
voice_id = voices(0).id if gender == "male" else voices(1).id
engine.setProperty("voice", voice_id)

engine.say(text)
engine.runAndWait()

Il flusso principale

Ora che abbiamo più o meno messo a posto tutti gli elementi della nostra app, è il momento di tuffarci nel gameplay! Il flusso principale è illustrato di seguito. Potresti notare alcune funzioni che non abbiamo approfondito (es choose_figure, play_round), ma puoi esplorare il codice completo tramite controllando il repository. Alla fine, la maggior parte di queste funzioni di livello superiore si collegano alle funzioni interne che abbiamo trattato sopra.

Ecco uno snippet del flusso di gioco principale:

import asyncio

from src.handle_transcript import text_to_speech
from src.main_flow_helpers import choose_figure, start, play_round, \
is_another_round

def farewell() -> None:
farewell_message = "It was great having you here, " \
"hope to see you again soon!"
print(f"\n{farewell_message}")
text_to_speech(farewell_message)

async def get_round_settings(figure: str) -> dict:
new_round_choice = await is_another_round()
if new_round_choice == "new figure":
return {"figure": "", "another_round": True}
elif new_round_choice == "no":
return {"figure": "", "another_round": False}
elif new_round_choice == "yes":
return {"figure": figure, "another_round": True}

async def main():
start()
another_round = True
figure = ""

while True:
if not figure:
figure = await choose_figure()

while another_round:
await play_round(chosen_figure=figure)
user_choices = await get_round_settings(figure)
figure, another_round = \
user_choices.get("figure"), user_choices.get("another_round")
if not figure:
break

if another_round is False:
farewell()
break

if __name__ == "__main__":
asyncio.run(main())

Avevamo in mente diverse idee che non siamo riusciti a realizzare durante l’hackathon. Ciò è dovuto al fatto che non abbiamo trovato un’API di cui fossimo soddisfatti durante quel fine settimana o ai vincoli di tempo che ci impedivano di sviluppare determinate funzionalità. Questi sono i percorsi che non abbiamo intrapreso per questo progetto:

Abbinare la voce di risposta con la voce “reale” della figura scelta

Immagina se l’utente scegliesse di parlare con Shrek, Trump o Oprah Winfrey. Volevamo che la nostra libreria di sintesi vocale o API articolasse le risposte utilizzando voci che corrispondessero alla figura scelta. Tuttavia, durante l’hackathon non siamo riusciti a trovare una libreria o un’API che offrisse questa funzionalità a un costo ragionevole. Siamo ancora aperti a suggerimenti se ne avete =)

Lascia che gli utenti parlino con “se stessi”

Un’altra idea interessante è stata quella di spingere gli utenti a fornire un campione vocale di se stessi mentre parlavano. Quindi addestreremmo un modello utilizzando questo esempio e faremmo leggere ad alta voce tutte le risposte generate da ChatGPT con la voce dell’utente. In questo scenario, l’utente potrebbe scegliere il tono delle risposte (affermativo e di supporto, sarcastico, arrabbiato, ecc.), ma la voce assomiglierebbe molto a quella dell’utente. Tuttavia, non siamo riusciti a trovare un’API che lo supportasse entro i limiti dell’hackathon.

Aggiunta di un frontend alla nostra applicazione

Il nostro piano iniziale era includere un componente frontend nella nostra applicazione. Tuttavia, a causa di un cambiamento dell’ultimo minuto nel numero dei partecipanti al nostro gruppo, abbiamo deciso di dare priorità allo sviluppo del backend. Di conseguenza, l’applicazione attualmente viene eseguita sull’interfaccia della riga di comando (CLI) e non ha un lato frontend.

La latenza è ciò che mi preoccupa di più in questo momento.

Ci sono diversi componenti nel flusso con una latenza relativamente elevata che a mio avviso danneggiano leggermente l’esperienza dell’utente. Ad esempio: il tempo impiegato dal completamento della fornitura dell’input audio e dalla ricezione di una trascrizione e il tempo impiegato dall’utente a premere un pulsante fino all’avvio effettivo della registrazione dell’audio da parte del sistema. Pertanto, se l’utente inizia a parlare subito dopo aver premuto il tasto, ci sarà almeno un secondo di audio che non verrà registrato a causa di questo ritardo.

Vuoi vedere l’intero progetto? È proprio qui!

Inoltre, va a loro un caloroso merito Lior Yardeniil mio partner di hackathon con cui ho creato questo gioco.

In questo articolo, abbiamo imparato come creare un gioco di sintesi vocale utilizzando Python e intrecciandolo con l’intelligenza artificiale. Abbiamo usato il Whisper modello di OpenAI per il riconoscimento vocale, ho giocato con il file FuzzyWuzzy libreria per la corrispondenza del testo, sfruttata ChatGPTtramite la loro API per sviluppatori e ha dato vita al tutto pyttsx3 per la sintesi vocale. Mentre OpenAIi servizi di (Whisper E ChatGPT per gli sviluppatori) hanno un costo modesto ed è conveniente.

Ci auguriamo che tu abbia trovato questa guida illuminante e che ti abbia motivato a intraprendere i tuoi progetti.

Saluti alla programmazione e al divertimento! 🚀

Fonte: towardsdatascience.com

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *