Skip to content

Commit

Permalink
Improve exception handling
Browse files Browse the repository at this point in the history
Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
  • Loading branch information
cweitkamp committed Jan 1, 2022
1 parent e4cd27f commit be1c72e
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 80 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
Expand All @@ -37,11 +36,13 @@
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.MimeTypes;
import org.openhab.core.audio.AudioFormat;
import org.openhab.core.auth.AuthenticationException;
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
import org.openhab.core.auth.client.oauth2.OAuthClientService;
import org.openhab.core.auth.client.oauth2.OAuthException;
import org.openhab.core.auth.client.oauth2.OAuthFactory;
import org.openhab.core.auth.client.oauth2.OAuthResponseException;
import org.openhab.core.i18n.CommunicationException;
import org.openhab.core.io.net.http.HttpRequestBuilder;
import org.openhab.voice.googletts.internal.protocol.AudioConfig;
import org.openhab.voice.googletts.internal.protocol.AudioEncoding;
Expand All @@ -59,6 +60,7 @@

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;

/**
* Google Cloud TTS API call implementation.
Expand Down Expand Up @@ -152,8 +154,8 @@ void setConfig(GoogleTTSConfig config) {
getAccessToken();
initialized = true;
initVoices();
} catch (AuthenticationException | IOException ex) {
logger.warn("Error initializing Google Cloud TTS service: {}", ex.getMessage());
} catch (AuthenticationException | CommunicationException e) {
logger.warn("Error initializing Google Cloud TTS service: {}", e.getMessage());
oAuthService = null;
initialized = false;
voices.clear();
Expand All @@ -177,17 +179,24 @@ void setConfig(GoogleTTSConfig config) {
/**
* Fetches the OAuth2 tokens from Google Cloud Platform if the auth-code is set in the configuration. If successful
* the auth-code will be removed from the configuration.
*
* @throws AuthenticationException
* @throws CommunicationException
*/
private void getAccessToken() throws AuthenticationException, IOException {
@SuppressWarnings("null")
private void getAccessToken() throws AuthenticationException, CommunicationException {
String authcode = config.authcode;
if (authcode != null && !authcode.isEmpty()) {
logger.debug("Trying to get access and refresh tokens.");
try {
oAuthService.getAccessTokenResponseByAuthorizationCode(authcode, GCP_REDIRECT_URI);
} catch (OAuthException | OAuthResponseException ex) {
logger.debug("Error fetching access token: {}", ex.getMessage(), ex);
} catch (OAuthException | OAuthResponseException e) {
logger.debug("Error fetching access token: {}", e.getMessage(), e);
throw new AuthenticationException(
"Error fetching access token. Invalid authcode? Please generate a new one.");
} catch (IOException e) {
throw new CommunicationException(
String.format("An unexpected IOException occured: %s", e.getMessage()));
}

config.authcode = null;
Expand All @@ -207,14 +216,17 @@ private void getAccessToken() throws AuthenticationException, IOException {
}
}

private String getAuthorizationHeader() throws AuthenticationException, IOException {
@SuppressWarnings("null")
private String getAuthorizationHeader() throws AuthenticationException, CommunicationException {
final AccessTokenResponse accessTokenResponse;
try {
accessTokenResponse = oAuthService.getAccessTokenResponse();
} catch (OAuthException | OAuthResponseException ex) {
logger.debug("Error fetching access token: {}", ex.getMessage(), ex);
} catch (OAuthException | OAuthResponseException e) {
logger.debug("Error fetching access token: {}", e.getMessage(), e);
throw new AuthenticationException(
"Error fetching access token. Invalid authcode? Please generate a new one.");
} catch (IOException e) {
throw new CommunicationException(String.format("An unexpected IOException occured: %s", e.getMessage()));
}
if (accessTokenResponse == null || accessTokenResponse.getAccessToken() == null
|| accessTokenResponse.getAccessToken().isEmpty()) {
Expand Down Expand Up @@ -255,13 +267,16 @@ Set<Locale> getSupportedLocales() {
*/
Set<GoogleTTSVoice> getVoicesForLocale(Locale locale) {
Set<GoogleTTSVoice> localeVoices = voices.get(locale);
return localeVoices != null ? localeVoices : Collections.emptySet();
return localeVoices != null ? localeVoices : Set.of();
}

/**
* Google API call to load locales and voices.
*
* @throws AuthenticationException
* @throws CommunicationException
*/
private void initVoices() throws AuthenticationException, IOException {
private void initVoices() throws AuthenticationException, CommunicationException {
if (oAuthService != null) {
voices.clear();
for (GoogleTTSVoice voice : listVoices()) {
Expand All @@ -281,25 +296,32 @@ private void initVoices() throws AuthenticationException, IOException {
}

@SuppressWarnings("null")
private List<GoogleTTSVoice> listVoices() throws AuthenticationException, IOException {
private List<GoogleTTSVoice> listVoices() throws AuthenticationException, CommunicationException {
HttpRequestBuilder builder = HttpRequestBuilder.getFrom(LIST_VOICES_URL)
.withHeader(HttpHeader.AUTHORIZATION.name(), getAuthorizationHeader());

ListVoicesResponse listVoicesResponse = gson.fromJson(builder.getContentAsString(), ListVoicesResponse.class);
try {
ListVoicesResponse listVoicesResponse = gson.fromJson(builder.getContentAsString(),
ListVoicesResponse.class);

if (listVoicesResponse == null || listVoicesResponse.getVoices() == null) {
return Collections.emptyList();
}
if (listVoicesResponse == null || listVoicesResponse.getVoices() == null) {
return List.of();
}

List<GoogleTTSVoice> result = new ArrayList<>();
for (Voice voice : listVoicesResponse.getVoices()) {
for (String languageCode : voice.getLanguageCodes()) {
result.add(new GoogleTTSVoice(Locale.forLanguageTag(languageCode), voice.getName(),
voice.getSsmlGender().name()));
List<GoogleTTSVoice> result = new ArrayList<>();
for (Voice voice : listVoicesResponse.getVoices()) {
for (String languageCode : voice.getLanguageCodes()) {
result.add(new GoogleTTSVoice(Locale.forLanguageTag(languageCode), voice.getName(),
voice.getSsmlGender().name()));
}
}
return result;
} catch (JsonSyntaxException e) {
// do nothing
} catch (IOException e) {
throw new CommunicationException(String.format("An unexpected IOException occured: %s", e.getMessage()));
}

return result;
return List.of();
}

/**
Expand All @@ -319,7 +341,7 @@ private String[] getFormatForCodec(String codec) {
}
}

byte[] synthesizeSpeech(String text, GoogleTTSVoice voice, String codec) {
public byte[] synthesizeSpeech(String text, GoogleTTSVoice voice, String codec) {
String[] format = getFormatForCodec(codec);
String fileNameInCache = getUniqueFilenameForText(text, voice.getTechnicalName());
File audioFileInCache = new File(cacheFolder, fileNameInCache + "." + format[1]);
Expand All @@ -336,19 +358,17 @@ byte[] synthesizeSpeech(String text, GoogleTTSVoice voice, String codec) {
saveAudioAndTextToFile(text, audioFileInCache, audio, voice.getTechnicalName());
}
return audio;
} catch (AuthenticationException ex) {
logger.warn("Error initializing Google Cloud TTS service: {}", ex.getMessage());
} catch (AuthenticationException | CommunicationException e) {
logger.warn("Error initializing Google Cloud TTS service: {}", e.getMessage());
oAuthService = null;
initialized = false;
voices.clear();
return null;
} catch (FileNotFoundException ex) {
logger.warn("Could not write {} to cache", audioFileInCache, ex);
return null;
} catch (IOException ex) {
logger.error("Could not write {} to cache", audioFileInCache, ex);
return null;
} catch (FileNotFoundException e) {
logger.warn("Could not write file {} to cache: ", audioFileInCache, e.getMessage());
} catch (IOException e) {
logger.debug("An unexpected IOException occured: %s", e.getMessage());
}
return null;
}

/**
Expand All @@ -358,10 +378,11 @@ byte[] synthesizeSpeech(String text, GoogleTTSVoice voice, String codec) {
* @param cacheFile Cache entry file.
* @param audio Byte array of the audio.
* @param voiceName Used voice
* @throws FileNotFoundException
* @throws IOException in case of file handling exceptions
*/
private void saveAudioAndTextToFile(String text, File cacheFile, byte[] audio, String voiceName)
throws IOException {
throws IOException, FileNotFoundException {
logger.debug("Caching audio file {}", cacheFile.getName());
try (FileOutputStream audioFileOutputStream = new FileOutputStream(cacheFile)) {
audioFileOutputStream.write(audio);
Expand Down Expand Up @@ -405,10 +426,12 @@ private String removeExtension(String fileName) {
* @param voice Voice parameter
* @param audioFormat Audio encoding format
* @return Audio input stream or {@code null} when encoding exceptions occur
* @throws AuthenticationException
* @throws CommunicationException
*/
@SuppressWarnings({ "null", "unused" })
@SuppressWarnings("null")
private byte[] synthesizeSpeechByGoogle(String text, GoogleTTSVoice voice, String audioFormat)
throws AuthenticationException, IOException {
throws AuthenticationException, CommunicationException {
AudioConfig audioConfig = new AudioConfig(AudioEncoding.valueOf(audioFormat), config.pitch, config.speakingRate,
config.volumeGainDb);
SynthesisInput synthesisInput = new SynthesisInput(text);
Expand All @@ -422,15 +445,22 @@ private byte[] synthesizeSpeechByGoogle(String text, GoogleTTSVoice voice, Strin
.withHeader(HttpHeader.AUTHORIZATION.name(), getAuthorizationHeader())
.withContent(gson.toJson(request), MimeTypes.Type.APPLICATION_JSON.name());

SynthesizeSpeechResponse synthesizeSpeechResponse = gson.fromJson(builder.getContentAsString(),
SynthesizeSpeechResponse.class);
try {
SynthesizeSpeechResponse synthesizeSpeechResponse = gson.fromJson(builder.getContentAsString(),
SynthesizeSpeechResponse.class);

if (synthesizeSpeechResponse == null) {
return null;
}
if (synthesizeSpeechResponse == null) {
return null;
}

byte[] encodedBytes = synthesizeSpeechResponse.getAudioContent().getBytes(StandardCharsets.UTF_8);
return Base64.getDecoder().decode(encodedBytes);
byte[] encodedBytes = synthesizeSpeechResponse.getAudioContent().getBytes(StandardCharsets.UTF_8);
return Base64.getDecoder().decode(encodedBytes);
} catch (JsonSyntaxException e) {
// do nothing
} catch (IOException e) {
throw new CommunicationException(String.format("An unexpected IOException occured: %s", e.getMessage()));
}
return null;
}

/**
Expand All @@ -445,9 +475,9 @@ private String getUniqueFilenameForText(String text, String voiceName) {
byte[] bytesOfMessage = (config.toConfigString() + text).getBytes(StandardCharsets.UTF_8);
String fileNameHash = String.format("%032x", new BigInteger(1, md.digest(bytesOfMessage)));
return voiceName + "_" + fileNameHash;
} catch (NoSuchAlgorithmException ex) {
} catch (NoSuchAlgorithmException e) {
// should not happen
logger.error("Could not create MD5 hash for '{}'", text, ex);
logger.error("Could not create MD5 hash for '{}'", text, e);
return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ public AudioStream synthesize(String text, Voice voice, AudioFormat requestedFor
// create the audio byte array for given text, locale, format
byte[] audio = apiImpl.synthesizeSpeech(trimmedText, (GoogleTTSVoice) voice, requestedFormat.getCodec());
if (audio == null) {
throw new TTSException("Could not read from Google Cloud TTS Service");
throw new TTSException("Could not synthesize text via Google Cloud TTS Service");
}
return new ByteArrayAudioStream(audio, requestedFormat);
}
Expand Down

0 comments on commit be1c72e

Please sign in to comment.