[googletts] Improve exception handling (#11925)
* Improve exception handling * Moved classes to dto package to reduce SAT warning Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
This commit is contained in:
parent
76855fd81a
commit
0936d97b41
|
@ -1,34 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.voice.googletts.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Thrown, if an authentication error is given.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AuthenticationException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public AuthenticationException() {
|
||||
}
|
||||
|
||||
public AuthenticationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -24,7 +24,6 @@ import java.security.NoSuchAlgorithmException;
|
|||
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;
|
||||
|
@ -37,21 +36,23 @@ import org.eclipse.jdt.annotation.Nullable;
|
|||
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;
|
||||
import org.openhab.voice.googletts.internal.protocol.ListVoicesResponse;
|
||||
import org.openhab.voice.googletts.internal.protocol.SsmlVoiceGender;
|
||||
import org.openhab.voice.googletts.internal.protocol.SynthesisInput;
|
||||
import org.openhab.voice.googletts.internal.protocol.SynthesizeSpeechRequest;
|
||||
import org.openhab.voice.googletts.internal.protocol.SynthesizeSpeechResponse;
|
||||
import org.openhab.voice.googletts.internal.protocol.Voice;
|
||||
import org.openhab.voice.googletts.internal.protocol.VoiceSelectionParams;
|
||||
import org.openhab.voice.googletts.internal.dto.AudioConfig;
|
||||
import org.openhab.voice.googletts.internal.dto.AudioEncoding;
|
||||
import org.openhab.voice.googletts.internal.dto.ListVoicesResponse;
|
||||
import org.openhab.voice.googletts.internal.dto.SsmlVoiceGender;
|
||||
import org.openhab.voice.googletts.internal.dto.SynthesisInput;
|
||||
import org.openhab.voice.googletts.internal.dto.SynthesizeSpeechRequest;
|
||||
import org.openhab.voice.googletts.internal.dto.SynthesizeSpeechResponse;
|
||||
import org.openhab.voice.googletts.internal.dto.Voice;
|
||||
import org.openhab.voice.googletts.internal.dto.VoiceSelectionParams;
|
||||
import org.osgi.service.cm.Configuration;
|
||||
import org.osgi.service.cm.ConfigurationAdmin;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -59,6 +60,7 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* Google Cloud TTS API call implementation.
|
||||
|
@ -152,8 +154,8 @@ class GoogleCloudAPI {
|
|||
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();
|
||||
|
@ -177,17 +179,24 @@ class GoogleCloudAPI {
|
|||
/**
|
||||
* 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 occurred: %s", e.getMessage()));
|
||||
}
|
||||
|
||||
config.authcode = null;
|
||||
|
@ -207,14 +216,17 @@ class GoogleCloudAPI {
|
|||
}
|
||||
}
|
||||
|
||||
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 occurred: %s", e.getMessage()));
|
||||
}
|
||||
if (accessTokenResponse == null || accessTokenResponse.getAccessToken() == null
|
||||
|| accessTokenResponse.getAccessToken().isEmpty()) {
|
||||
|
@ -255,13 +267,16 @@ class GoogleCloudAPI {
|
|||
*/
|
||||
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()) {
|
||||
|
@ -281,25 +296,32 @@ class GoogleCloudAPI {
|
|||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
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()));
|
||||
if (listVoicesResponse == null || listVoicesResponse.getVoices() == null) {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
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 occurred: %s", e.getMessage()));
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -319,7 +341,7 @@ class GoogleCloudAPI {
|
|||
}
|
||||
}
|
||||
|
||||
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]);
|
||||
|
@ -336,19 +358,17 @@ class GoogleCloudAPI {
|
|||
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 occurred: {}", e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -358,10 +378,11 @@ class GoogleCloudAPI {
|
|||
* @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);
|
||||
|
@ -405,10 +426,12 @@ class GoogleCloudAPI {
|
|||
* @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);
|
||||
|
@ -422,15 +445,22 @@ class GoogleCloudAPI {
|
|||
.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);
|
||||
} catch (JsonSyntaxException e) {
|
||||
// do nothing
|
||||
} catch (IOException e) {
|
||||
throw new CommunicationException(String.format("An unexpected IOException occurred: %s", e.getMessage()));
|
||||
}
|
||||
|
||||
byte[] encodedBytes = synthesizeSpeechResponse.getAudioContent().getBytes(StandardCharsets.UTF_8);
|
||||
return Base64.getDecoder().decode(encodedBytes);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -445,9 +475,9 @@ class GoogleCloudAPI {
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ import org.openhab.core.config.core.ConfigurableService;
|
|||
import org.openhab.core.voice.TTSException;
|
||||
import org.openhab.core.voice.TTSService;
|
||||
import org.openhab.core.voice.Voice;
|
||||
import org.openhab.voice.googletts.internal.protocol.AudioEncoding;
|
||||
import org.openhab.voice.googletts.internal.dto.AudioEncoding;
|
||||
import org.osgi.framework.Constants;
|
||||
import org.osgi.service.cm.ConfigurationAdmin;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
|
@ -330,7 +330,7 @@ public class GoogleTTSService implements TTSService {
|
|||
// 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);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import java.util.Locale;
|
|||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.voice.Voice;
|
||||
import org.openhab.voice.googletts.internal.protocol.SsmlVoiceGender;
|
||||
import org.openhab.voice.googletts.internal.dto.SsmlVoiceGender;
|
||||
|
||||
/**
|
||||
* Implementation of the Voice interface for Google Cloud TTS Service.
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.voice.googletts.internal.protocol;
|
||||
package org.openhab.voice.googletts.internal.dto;
|
||||
|
||||
/**
|
||||
* The configuration of the synthesized audio.
|
|
@ -10,7 +10,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.voice.googletts.internal.protocol;
|
||||
package org.openhab.voice.googletts.internal.dto;
|
||||
|
||||
/**
|
||||
* Configuration to set up audio encoder.
|
|
@ -10,7 +10,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.voice.googletts.internal.protocol;
|
||||
package org.openhab.voice.googletts.internal.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.voice.googletts.internal.protocol;
|
||||
package org.openhab.voice.googletts.internal.dto;
|
||||
|
||||
/**
|
||||
* Gender of the voice as described in SSML voice element.
|
|
@ -10,7 +10,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.voice.googletts.internal.protocol;
|
||||
package org.openhab.voice.googletts.internal.dto;
|
||||
|
||||
/**
|
||||
* Contains text input to be synthesized. Either text or ssml must be supplied. Supplying both or neither returns
|
|
@ -10,7 +10,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.voice.googletts.internal.protocol;
|
||||
package org.openhab.voice.googletts.internal.dto;
|
||||
|
||||
/**
|
||||
* Synthesizes speech synchronously: receive results after all text input has been processed.
|
|
@ -10,7 +10,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.voice.googletts.internal.protocol;
|
||||
package org.openhab.voice.googletts.internal.dto;
|
||||
|
||||
/**
|
||||
* The message returned to the client by the text.synthesize method.
|
|
@ -10,7 +10,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.voice.googletts.internal.protocol;
|
||||
package org.openhab.voice.googletts.internal.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.voice.googletts.internal.protocol;
|
||||
package org.openhab.voice.googletts.internal.dto;
|
||||
|
||||
/**
|
||||
* Description of which voice to use for a synthesis request.
|
Loading…
Reference in New Issue