+++ /dev/null
-/**
- * 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);
- }
-}
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;
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;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
+import com.google.gson.JsonSyntaxException;
/**
* Google Cloud TTS API call implementation.
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();
/**
* 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;
}
}
- 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()) {
*/
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()) {
}
@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 occurred: %s", e.getMessage()));
}
-
- return result;
+ return List.of();
}
/**
}
}
- 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]);
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;
}
/**
* @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);
* @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);
.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 occurred: %s", e.getMessage()));
+ }
+ return null;
}
/**
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;
}
}
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;
// 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);
}
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.
--- /dev/null
+/**
+ * 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.dto;
+
+/**
+ * The configuration of the synthesized audio.
+ *
+ * @author Wouter Born - Initial contribution
+ */
+public class AudioConfig {
+
+ /**
+ * Required. The format of the requested audio byte stream.
+ */
+ private AudioEncoding audioEncoding;
+
+ /**
+ * Optional speaking pitch, in the range [-20.0, 20.0]. 20 means increase 20 semitones from the original pitch. -20
+ * means decrease 20 semitones from the original pitch.
+ */
+ private Double pitch;
+
+ /**
+ * The synthesis sample rate (in hertz) for this audio. Optional. If this is different from the voice's natural
+ * sample rate, then the synthesizer will honor this request by converting to the desired sample rate (which might
+ * result in worse audio quality), unless the specified sample rate is not supported for the encoding chosen, in
+ * which case it will fail the request and return google.rpc.Code.INVALID_ARGUMENT.
+ */
+ private Long sampleRateHertz;
+
+ /**
+ * Optional speaking rate/speed, in the range [0.25, 4.0]. 1.0 is the normal native speed supported by the specific
+ * voice. 2.0 is twice as fast, and 0.5 is half as fast. If unset(0.0), defaults to the native 1.0 speed. Any other
+ * values < 0.25 or > 4.0 will return an error.
+ */
+ private Double speakingRate;
+
+ /**
+ * Optional volume gain (in dB) of the normal native volume supported by the specific voice, in the range [-96.0,
+ * 16.0]. If unset, or set to a value of 0.0 (dB), will play at normal native signal amplitude. A value of -6.0 (dB)
+ * will play at approximately half the amplitude of the normal native signal amplitude. A value of +6.0 (dB) will
+ * play at approximately twice the amplitude of the normal native signal amplitude. Strongly recommend not to exceed
+ * +10 (dB) as there's usually no effective increase in loudness for any value greater than that.
+ */
+ private Double volumeGainDb;
+
+ public AudioConfig() {
+ }
+
+ public AudioConfig(AudioEncoding audioEncoding, Double pitch, Double speakingRate, Double volumeGainDb) {
+ this(audioEncoding, pitch, null, speakingRate, volumeGainDb);
+ }
+
+ public AudioConfig(AudioEncoding audioEncoding, Double pitch, Long sampleRateHertz, Double speakingRate,
+ Double volumeGainDb) {
+ this.audioEncoding = audioEncoding;
+ this.pitch = pitch;
+ this.sampleRateHertz = sampleRateHertz;
+ this.speakingRate = speakingRate;
+ this.volumeGainDb = volumeGainDb;
+ }
+
+ public AudioEncoding getAudioEncoding() {
+ return audioEncoding;
+ }
+
+ public Double getPitch() {
+ return pitch;
+ }
+
+ public Long getSampleRateHertz() {
+ return sampleRateHertz;
+ }
+
+ public Double getSpeakingRate() {
+ return speakingRate;
+ }
+
+ public Double getVolumeGainDb() {
+ return volumeGainDb;
+ }
+
+ public void setAudioEncoding(AudioEncoding audioEncoding) {
+ this.audioEncoding = audioEncoding;
+ }
+
+ public void setPitch(Double pitch) {
+ this.pitch = pitch;
+ }
+
+ public void setSampleRateHertz(Long sampleRateHertz) {
+ this.sampleRateHertz = sampleRateHertz;
+ }
+
+ public void setSpeakingRate(Double speakingRate) {
+ this.speakingRate = speakingRate;
+ }
+
+ public void setVolumeGainDb(Double volumeGainDb) {
+ this.volumeGainDb = volumeGainDb;
+ }
+}
--- /dev/null
+/**
+ * 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.dto;
+
+/**
+ * Configuration to set up audio encoder.
+ *
+ * @author Wouter Born - Initial contribution
+ */
+public enum AudioEncoding {
+
+ /**
+ * Not specified.
+ */
+ AUDIO_ENCODING_UNSPECIFIED,
+
+ /**
+ * Uncompressed 16-bit signed little-endian samples (Linear PCM). Audio content returned as LINEAR16 also contains a
+ * WAV header.
+ */
+ LINEAR16,
+
+ /**
+ * MP3 audio.
+ */
+ MP3,
+
+ /**
+ * Opus encoded audio wrapped in an ogg container. The result will be a file which can be played natively on
+ * Android, and in browsers (at least Chrome and Firefox). The quality of the encoding is considerably higher than
+ * MP3 while using approximately the same bitrate.
+ */
+ OGG_OPUS
+}
--- /dev/null
+/**
+ * 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.dto;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The message returned to the client by the voices.list method.
+ *
+ * @author Wouter Born - Initial contribution
+ */
+@NonNullByDefault
+public class ListVoicesResponse {
+
+ /**
+ * The list of voices.
+ */
+ private @Nullable List<Voice> voices;
+
+ public @Nullable List<Voice> getVoices() {
+ return voices;
+ }
+
+ public void setVoices(List<Voice> voices) {
+ this.voices = voices;
+ }
+}
--- /dev/null
+/**
+ * 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.dto;
+
+/**
+ * Gender of the voice as described in SSML voice element.
+ *
+ * @author Wouter Born - Initial contribution
+ */
+public enum SsmlVoiceGender {
+
+ /**
+ * An unspecified gender. In VoiceSelectionParams, this means that the client doesn't care which gender the selected
+ * voice will have. In the Voice field of ListVoicesResponse, this may mean that the voice doesn't fit any of the
+ * other categories in this enum, or that the gender of the voice isn't known.
+ */
+ SSML_VOICE_GENDER_UNSPECIFIED,
+
+ /**
+ * A male voice.
+ */
+ MALE,
+
+ /**
+ * A female voice.
+ */
+ FEMALE,
+
+ /**
+ * A gender-neutral voice.
+ */
+ NEUTRAL
+
+}
--- /dev/null
+/**
+ * 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.dto;
+
+/**
+ * Contains text input to be synthesized. Either text or ssml must be supplied. Supplying both or neither returns
+ * google.rpc.Code.INVALID_ARGUMENT. The input size is limited to 5000 characters.
+ *
+ * @author Wouter Born - Initial contribution
+ */
+public class SynthesisInput {
+
+ /**
+ * The SSML document to be synthesized. The SSML document must be valid and well-formed. Otherwise the RPC will fail
+ * and return google.rpc.Code.INVALID_ARGUMENT.
+ */
+ private String ssml;
+
+ /**
+ * The raw text to be synthesized.
+ */
+ private String text;
+
+ public SynthesisInput() {
+ }
+
+ public SynthesisInput(String text) {
+ if (text.startsWith("<speak>")) {
+ ssml = text;
+ } else {
+ this.text = text;
+ }
+ }
+
+ public String getSsml() {
+ return ssml;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setSsml(String ssml) {
+ this.ssml = ssml;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+}
--- /dev/null
+/**
+ * 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.dto;
+
+/**
+ * Synthesizes speech synchronously: receive results after all text input has been processed.
+ *
+ * @author Wouter Born - Initial contribution
+ */
+public class SynthesizeSpeechRequest {
+
+ /**
+ * Required. The configuration of the synthesized audio.
+ */
+ private AudioConfig audioConfig = new AudioConfig();
+
+ /**
+ * Required. The Synthesizer requires either plain text or SSML as input.
+ */
+ private SynthesisInput input = new SynthesisInput();
+
+ /**
+ * Required. The desired voice of the synthesized audio.
+ */
+ private VoiceSelectionParams voice = new VoiceSelectionParams();
+
+ public SynthesizeSpeechRequest() {
+ }
+
+ public SynthesizeSpeechRequest(AudioConfig audioConfig, SynthesisInput input, VoiceSelectionParams voice) {
+ this.audioConfig = audioConfig;
+ this.input = input;
+ this.voice = voice;
+ }
+
+ public AudioConfig getAudioConfig() {
+ return audioConfig;
+ }
+
+ public SynthesisInput getInput() {
+ return input;
+ }
+
+ public VoiceSelectionParams getVoice() {
+ return voice;
+ }
+
+ public void setAudioConfig(AudioConfig audioConfig) {
+ this.audioConfig = audioConfig;
+ }
+
+ public void setInput(SynthesisInput input) {
+ this.input = input;
+ }
+
+ public void setVoice(VoiceSelectionParams voice) {
+ this.voice = voice;
+ }
+}
--- /dev/null
+/**
+ * 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.dto;
+
+/**
+ * The message returned to the client by the text.synthesize method.
+ *
+ * @author Wouter Born - Initial contribution
+ */
+public class SynthesizeSpeechResponse {
+
+ /**
+ * The audio data bytes encoded as specified in the request, including the header (For LINEAR16 audio, we include
+ * the WAV header). Note: as with all bytes fields, protobuffers use a pure binary representation, whereas JSON
+ * representations use base64.
+ *
+ * A base64-encoded string.
+ */
+ private String audioContent;
+
+ public String getAudioContent() {
+ return audioContent;
+ }
+
+ public void setAudioContent(String audioContent) {
+ this.audioContent = audioContent;
+ }
+}
--- /dev/null
+/**
+ * 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.dto;
+
+import java.util.List;
+
+/**
+ * Description of a voice supported by the TTS service.
+ *
+ * @author Wouter Born - Initial contribution
+ */
+public class Voice {
+
+ /**
+ * The languages that this voice supports, expressed as BCP-47 language tags (e.g. "en-US", "es-419", "cmn-tw").
+ */
+ private List<String> languageCodes;
+
+ /**
+ * The name of this voice. Each distinct voice has a unique name.
+ */
+ private String name;
+
+ /**
+ * The natural sample rate (in hertz) for this voice.
+ */
+ private Long naturalSampleRateHertz;
+
+ /**
+ * The gender of this voice.
+ */
+ private SsmlVoiceGender ssmlGender;
+
+ public List<String> getLanguageCodes() {
+ return languageCodes;
+ }
+
+ public void setLanguageCodes(List<String> languageCodes) {
+ this.languageCodes = languageCodes;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Long getNaturalSampleRateHertz() {
+ return naturalSampleRateHertz;
+ }
+
+ public void setNaturalSampleRateHertz(Long naturalSampleRateHertz) {
+ this.naturalSampleRateHertz = naturalSampleRateHertz;
+ }
+
+ public SsmlVoiceGender getSsmlGender() {
+ return ssmlGender;
+ }
+
+ public void setSsmlGender(SsmlVoiceGender ssmlGender) {
+ this.ssmlGender = ssmlGender;
+ }
+}
--- /dev/null
+/**
+ * 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.dto;
+
+/**
+ * Description of which voice to use for a synthesis request.
+ *
+ * @author Wouter Born - Initial contribution
+ */
+public class VoiceSelectionParams {
+
+ /**
+ * The language (and optionally also the region) of the voice expressed as a BCP-47 language tag, e.g. "en-US".
+ * Required. This should not include a script tag (e.g. use "cmn-cn" rather than "cmn-Hant-cn"), because the script
+ * will be inferred from the input provided in the SynthesisInput. The TTS service will use this parameter to help
+ * choose an appropriate voice. Note that the TTS service may choose a voice with a slightly different language code
+ * than the one selected; it may substitute a different region (e.g. using en-US rather than en-CA if there isn't a
+ * Canadian voice available), or even a different language, e.g. using "nb" (Norwegian Bokmal) instead of "no"
+ * (Norwegian)".
+ */
+ private String languageCode;
+
+ /**
+ * The name of the voice. Optional; if not set, the service will choose a voice based on the other parameters such
+ * as languageCode and gender.
+ */
+ private String name;
+
+ /**
+ * The preferred gender of the voice. Optional; if not set, the service will choose a voice based on the other
+ * parameters such as languageCode and name. Note that this is only a preference, not requirement; if a voice of the
+ * appropriate gender is not available, the synthesizer should substitute a voice with a different gender rather
+ * than failing the request.
+ */
+ private SsmlVoiceGender ssmlGender;
+
+ public VoiceSelectionParams() {
+ }
+
+ public VoiceSelectionParams(String languageCode, String name, SsmlVoiceGender ssmlGender) {
+ this.languageCode = languageCode;
+ this.name = name;
+ this.ssmlGender = ssmlGender;
+ }
+
+ public String getLanguageCode() {
+ return languageCode;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public SsmlVoiceGender getSsmlGender() {
+ return ssmlGender;
+ }
+
+ public void setLanguageCode(String languageCode) {
+ this.languageCode = languageCode;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setSsmlGender(SsmlVoiceGender ssmlGender) {
+ this.ssmlGender = ssmlGender;
+ }
+}
+++ /dev/null
-/**
- * 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.protocol;
-
-/**
- * The configuration of the synthesized audio.
- *
- * @author Wouter Born - Initial contribution
- */
-public class AudioConfig {
-
- /**
- * Required. The format of the requested audio byte stream.
- */
- private AudioEncoding audioEncoding;
-
- /**
- * Optional speaking pitch, in the range [-20.0, 20.0]. 20 means increase 20 semitones from the original pitch. -20
- * means decrease 20 semitones from the original pitch.
- */
- private Double pitch;
-
- /**
- * The synthesis sample rate (in hertz) for this audio. Optional. If this is different from the voice's natural
- * sample rate, then the synthesizer will honor this request by converting to the desired sample rate (which might
- * result in worse audio quality), unless the specified sample rate is not supported for the encoding chosen, in
- * which case it will fail the request and return google.rpc.Code.INVALID_ARGUMENT.
- */
- private Long sampleRateHertz;
-
- /**
- * Optional speaking rate/speed, in the range [0.25, 4.0]. 1.0 is the normal native speed supported by the specific
- * voice. 2.0 is twice as fast, and 0.5 is half as fast. If unset(0.0), defaults to the native 1.0 speed. Any other
- * values < 0.25 or > 4.0 will return an error.
- */
- private Double speakingRate;
-
- /**
- * Optional volume gain (in dB) of the normal native volume supported by the specific voice, in the range [-96.0,
- * 16.0]. If unset, or set to a value of 0.0 (dB), will play at normal native signal amplitude. A value of -6.0 (dB)
- * will play at approximately half the amplitude of the normal native signal amplitude. A value of +6.0 (dB) will
- * play at approximately twice the amplitude of the normal native signal amplitude. Strongly recommend not to exceed
- * +10 (dB) as there's usually no effective increase in loudness for any value greater than that.
- */
- private Double volumeGainDb;
-
- public AudioConfig() {
- }
-
- public AudioConfig(AudioEncoding audioEncoding, Double pitch, Double speakingRate, Double volumeGainDb) {
- this(audioEncoding, pitch, null, speakingRate, volumeGainDb);
- }
-
- public AudioConfig(AudioEncoding audioEncoding, Double pitch, Long sampleRateHertz, Double speakingRate,
- Double volumeGainDb) {
- this.audioEncoding = audioEncoding;
- this.pitch = pitch;
- this.sampleRateHertz = sampleRateHertz;
- this.speakingRate = speakingRate;
- this.volumeGainDb = volumeGainDb;
- }
-
- public AudioEncoding getAudioEncoding() {
- return audioEncoding;
- }
-
- public Double getPitch() {
- return pitch;
- }
-
- public Long getSampleRateHertz() {
- return sampleRateHertz;
- }
-
- public Double getSpeakingRate() {
- return speakingRate;
- }
-
- public Double getVolumeGainDb() {
- return volumeGainDb;
- }
-
- public void setAudioEncoding(AudioEncoding audioEncoding) {
- this.audioEncoding = audioEncoding;
- }
-
- public void setPitch(Double pitch) {
- this.pitch = pitch;
- }
-
- public void setSampleRateHertz(Long sampleRateHertz) {
- this.sampleRateHertz = sampleRateHertz;
- }
-
- public void setSpeakingRate(Double speakingRate) {
- this.speakingRate = speakingRate;
- }
-
- public void setVolumeGainDb(Double volumeGainDb) {
- this.volumeGainDb = volumeGainDb;
- }
-}
+++ /dev/null
-/**
- * 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.protocol;
-
-/**
- * Configuration to set up audio encoder.
- *
- * @author Wouter Born - Initial contribution
- */
-public enum AudioEncoding {
-
- /**
- * Not specified.
- */
- AUDIO_ENCODING_UNSPECIFIED,
-
- /**
- * Uncompressed 16-bit signed little-endian samples (Linear PCM). Audio content returned as LINEAR16 also contains a
- * WAV header.
- */
- LINEAR16,
-
- /**
- * MP3 audio.
- */
- MP3,
-
- /**
- * Opus encoded audio wrapped in an ogg container. The result will be a file which can be played natively on
- * Android, and in browsers (at least Chrome and Firefox). The quality of the encoding is considerably higher than
- * MP3 while using approximately the same bitrate.
- */
- OGG_OPUS
-}
+++ /dev/null
-/**
- * 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.protocol;
-
-import java.util.List;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-
-/**
- * The message returned to the client by the voices.list method.
- *
- * @author Wouter Born - Initial contribution
- */
-@NonNullByDefault
-public class ListVoicesResponse {
-
- /**
- * The list of voices.
- */
- private @Nullable List<Voice> voices;
-
- public @Nullable List<Voice> getVoices() {
- return voices;
- }
-
- public void setVoices(List<Voice> voices) {
- this.voices = voices;
- }
-}
+++ /dev/null
-/**
- * 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.protocol;
-
-/**
- * Gender of the voice as described in SSML voice element.
- *
- * @author Wouter Born - Initial contribution
- */
-public enum SsmlVoiceGender {
-
- /**
- * An unspecified gender. In VoiceSelectionParams, this means that the client doesn't care which gender the selected
- * voice will have. In the Voice field of ListVoicesResponse, this may mean that the voice doesn't fit any of the
- * other categories in this enum, or that the gender of the voice isn't known.
- */
- SSML_VOICE_GENDER_UNSPECIFIED,
-
- /**
- * A male voice.
- */
- MALE,
-
- /**
- * A female voice.
- */
- FEMALE,
-
- /**
- * A gender-neutral voice.
- */
- NEUTRAL
-
-}
+++ /dev/null
-/**
- * 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.protocol;
-
-/**
- * Contains text input to be synthesized. Either text or ssml must be supplied. Supplying both or neither returns
- * google.rpc.Code.INVALID_ARGUMENT. The input size is limited to 5000 characters.
- *
- * @author Wouter Born - Initial contribution
- */
-public class SynthesisInput {
-
- /**
- * The SSML document to be synthesized. The SSML document must be valid and well-formed. Otherwise the RPC will fail
- * and return google.rpc.Code.INVALID_ARGUMENT.
- */
- private String ssml;
-
- /**
- * The raw text to be synthesized.
- */
- private String text;
-
- public SynthesisInput() {
- }
-
- public SynthesisInput(String text) {
- if (text.startsWith("<speak>")) {
- ssml = text;
- } else {
- this.text = text;
- }
- }
-
- public String getSsml() {
- return ssml;
- }
-
- public String getText() {
- return text;
- }
-
- public void setSsml(String ssml) {
- this.ssml = ssml;
- }
-
- public void setText(String text) {
- this.text = text;
- }
-}
+++ /dev/null
-/**
- * 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.protocol;
-
-/**
- * Synthesizes speech synchronously: receive results after all text input has been processed.
- *
- * @author Wouter Born - Initial contribution
- */
-public class SynthesizeSpeechRequest {
-
- /**
- * Required. The configuration of the synthesized audio.
- */
- private AudioConfig audioConfig = new AudioConfig();
-
- /**
- * Required. The Synthesizer requires either plain text or SSML as input.
- */
- private SynthesisInput input = new SynthesisInput();
-
- /**
- * Required. The desired voice of the synthesized audio.
- */
- private VoiceSelectionParams voice = new VoiceSelectionParams();
-
- public SynthesizeSpeechRequest() {
- }
-
- public SynthesizeSpeechRequest(AudioConfig audioConfig, SynthesisInput input, VoiceSelectionParams voice) {
- this.audioConfig = audioConfig;
- this.input = input;
- this.voice = voice;
- }
-
- public AudioConfig getAudioConfig() {
- return audioConfig;
- }
-
- public SynthesisInput getInput() {
- return input;
- }
-
- public VoiceSelectionParams getVoice() {
- return voice;
- }
-
- public void setAudioConfig(AudioConfig audioConfig) {
- this.audioConfig = audioConfig;
- }
-
- public void setInput(SynthesisInput input) {
- this.input = input;
- }
-
- public void setVoice(VoiceSelectionParams voice) {
- this.voice = voice;
- }
-}
+++ /dev/null
-/**
- * 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.protocol;
-
-/**
- * The message returned to the client by the text.synthesize method.
- *
- * @author Wouter Born - Initial contribution
- */
-public class SynthesizeSpeechResponse {
-
- /**
- * The audio data bytes encoded as specified in the request, including the header (For LINEAR16 audio, we include
- * the WAV header). Note: as with all bytes fields, protobuffers use a pure binary representation, whereas JSON
- * representations use base64.
- *
- * A base64-encoded string.
- */
- private String audioContent;
-
- public String getAudioContent() {
- return audioContent;
- }
-
- public void setAudioContent(String audioContent) {
- this.audioContent = audioContent;
- }
-}
+++ /dev/null
-/**
- * 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.protocol;
-
-import java.util.List;
-
-/**
- * Description of a voice supported by the TTS service.
- *
- * @author Wouter Born - Initial contribution
- */
-public class Voice {
-
- /**
- * The languages that this voice supports, expressed as BCP-47 language tags (e.g. "en-US", "es-419", "cmn-tw").
- */
- private List<String> languageCodes;
-
- /**
- * The name of this voice. Each distinct voice has a unique name.
- */
- private String name;
-
- /**
- * The natural sample rate (in hertz) for this voice.
- */
- private Long naturalSampleRateHertz;
-
- /**
- * The gender of this voice.
- */
- private SsmlVoiceGender ssmlGender;
-
- public List<String> getLanguageCodes() {
- return languageCodes;
- }
-
- public void setLanguageCodes(List<String> languageCodes) {
- this.languageCodes = languageCodes;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public Long getNaturalSampleRateHertz() {
- return naturalSampleRateHertz;
- }
-
- public void setNaturalSampleRateHertz(Long naturalSampleRateHertz) {
- this.naturalSampleRateHertz = naturalSampleRateHertz;
- }
-
- public SsmlVoiceGender getSsmlGender() {
- return ssmlGender;
- }
-
- public void setSsmlGender(SsmlVoiceGender ssmlGender) {
- this.ssmlGender = ssmlGender;
- }
-}
+++ /dev/null
-/**
- * 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.protocol;
-
-/**
- * Description of which voice to use for a synthesis request.
- *
- * @author Wouter Born - Initial contribution
- */
-public class VoiceSelectionParams {
-
- /**
- * The language (and optionally also the region) of the voice expressed as a BCP-47 language tag, e.g. "en-US".
- * Required. This should not include a script tag (e.g. use "cmn-cn" rather than "cmn-Hant-cn"), because the script
- * will be inferred from the input provided in the SynthesisInput. The TTS service will use this parameter to help
- * choose an appropriate voice. Note that the TTS service may choose a voice with a slightly different language code
- * than the one selected; it may substitute a different region (e.g. using en-US rather than en-CA if there isn't a
- * Canadian voice available), or even a different language, e.g. using "nb" (Norwegian Bokmal) instead of "no"
- * (Norwegian)".
- */
- private String languageCode;
-
- /**
- * The name of the voice. Optional; if not set, the service will choose a voice based on the other parameters such
- * as languageCode and gender.
- */
- private String name;
-
- /**
- * The preferred gender of the voice. Optional; if not set, the service will choose a voice based on the other
- * parameters such as languageCode and name. Note that this is only a preference, not requirement; if a voice of the
- * appropriate gender is not available, the synthesizer should substitute a voice with a different gender rather
- * than failing the request.
- */
- private SsmlVoiceGender ssmlGender;
-
- public VoiceSelectionParams() {
- }
-
- public VoiceSelectionParams(String languageCode, String name, SsmlVoiceGender ssmlGender) {
- this.languageCode = languageCode;
- this.name = name;
- this.ssmlGender = ssmlGender;
- }
-
- public String getLanguageCode() {
- return languageCode;
- }
-
- public String getName() {
- return name;
- }
-
- public SsmlVoiceGender getSsmlGender() {
- return ssmlGender;
- }
-
- public void setLanguageCode(String languageCode) {
- this.languageCode = languageCode;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public void setSsmlGender(SsmlVoiceGender ssmlGender) {
- this.ssmlGender = ssmlGender;
- }
-}