diff --git a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSTTSService.java b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSTTSService.java index 73eecbec8..ca25f7342 100644 --- a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSTTSService.java +++ b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSTTSService.java @@ -20,6 +20,8 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.OpenHAB; import org.openhab.core.audio.AudioException; import org.openhab.core.audio.AudioFormat; @@ -30,6 +32,7 @@ import org.openhab.core.voice.TTSService; import org.openhab.core.voice.Voice; import org.openhab.voice.voicerss.internal.cloudapi.CachedVoiceRSSCloudImpl; import org.osgi.framework.Constants; +import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Modified; import org.slf4j.Logger; @@ -41,6 +44,7 @@ import org.slf4j.LoggerFactory; * @author Jochen Hiller - Initial contribution and API * @author Laurent Garnier - add support for OGG and AAC audio formats */ +@NonNullByDefault @Component(configurationPid = "org.openhab.voicerss", property = Constants.SERVICE_PID + "=org.openhab.voicerss") @ConfigurableService(category = "voice", label = "VoiceRSS Text-to-Speech", description_uri = "voice:voicerss") public class VoiceRSSTTSService implements TTSService { @@ -66,27 +70,28 @@ public class VoiceRSSTTSService implements TTSService { private final Logger logger = LoggerFactory.getLogger(VoiceRSSTTSService.class); - private String apiKey; + private @Nullable String apiKey; /** * We need the cached implementation to allow for FixedLengthAudioStream. */ - private CachedVoiceRSSCloudImpl voiceRssImpl; + private @Nullable CachedVoiceRSSCloudImpl voiceRssImpl; /** * Set of supported voices */ - private Set voices; + private @Nullable Set voices; /** * Set of supported audio formats */ - private Set audioFormats; + private @Nullable Set audioFormats; /** * DS activate, with access to ConfigAdmin */ - protected void activate(Map config) { + @Activate + protected void activate(@Nullable Map config) { try { modified(config); voiceRssImpl = initVoiceImplementation(); @@ -100,7 +105,7 @@ public class VoiceRSSTTSService implements TTSService { } @Modified - protected void modified(Map config) { + protected void modified(@Nullable Map config) { if (config != null) { apiKey = config.containsKey(CONFIG_API_KEY) ? config.get(CONFIG_API_KEY).toString() : null; } @@ -108,37 +113,41 @@ public class VoiceRSSTTSService implements TTSService { @Override public Set getAvailableVoices() { - return Collections.unmodifiableSet(voices); + Set localVoices = voices; + return localVoices == null ? Set.of() : Collections.unmodifiableSet(localVoices); } @Override public Set getSupportedFormats() { - return Collections.unmodifiableSet(audioFormats); + Set localFormats = audioFormats; + return localFormats == null ? Set.of() : Collections.unmodifiableSet(localFormats); } @Override public AudioStream synthesize(String text, Voice voice, AudioFormat requestedFormat) throws TTSException { logger.debug("Synthesize '{}' for voice '{}' in format {}", text, voice.getUID(), requestedFormat); - // Validate known api key - if (apiKey == null) { - throw new TTSException("Missing API key, configure it first before using"); + CachedVoiceRSSCloudImpl voiceRssCloud = voiceRssImpl; + if (voiceRssCloud == null) { + throw new TTSException("The service is not correctly initialized"); } - // Validate arguments - if (text == null) { - throw new TTSException("The passed text is null"); + // Validate known api key + String key = apiKey; + if (key == null) { + throw new TTSException("Missing API key, configure it first before using"); } // trim text String trimmedText = text.trim(); if (trimmedText.isEmpty()) { throw new TTSException("The passed text is empty"); } - if (!voices.contains(voice)) { + Set localVoices = voices; + if (localVoices == null || !localVoices.contains(voice)) { throw new TTSException("The passed voice is unsupported"); } // now create the input stream for given text, locale, voice, codec and format. try { - File cacheAudioFile = voiceRssImpl.getTextToSpeechAsFile(apiKey, trimmedText, + File cacheAudioFile = voiceRssCloud.getTextToSpeechAsFile(key, trimmedText, voice.getLocale().toLanguageTag(), voice.getLabel(), getApiAudioCodec(requestedFormat), getApiAudioFormat(requestedFormat)); return new VoiceRSSAudioStream(cacheAudioFile, requestedFormat); @@ -153,11 +162,16 @@ public class VoiceRSSTTSService implements TTSService { * Initializes voices. * * @return The voices of this instance + * @throws IllegalStateException if voiceRssImpl is null */ - private Set initVoices() { + private Set initVoices() throws IllegalStateException { + CachedVoiceRSSCloudImpl voiceRssCloud = voiceRssImpl; + if (voiceRssCloud == null) { + throw new IllegalStateException("The service is not correctly initialized"); + } Set voices = new HashSet<>(); - for (Locale locale : voiceRssImpl.getAvailableLocales()) { - for (String voiceLabel : voiceRssImpl.getAvailableVoices(locale)) { + for (Locale locale : voiceRssCloud.getAvailableLocales()) { + for (String voiceLabel : voiceRssCloud.getAvailableVoices(locale)) { voices.add(new VoiceRSSVoice(locale, voiceLabel)); } } @@ -168,10 +182,15 @@ public class VoiceRSSTTSService implements TTSService { * Initializes audioFormats * * @return The audio formats of this instance + * @throws IllegalStateException if voiceRssImpl is null */ - private Set initAudioFormats() { + private Set initAudioFormats() throws IllegalStateException { + CachedVoiceRSSCloudImpl voiceRssCloud = voiceRssImpl; + if (voiceRssCloud == null) { + throw new IllegalStateException("The service is not correctly initialized"); + } Set audioFormats = new HashSet<>(); - for (String codec : voiceRssImpl.getAvailableAudioCodecs()) { + for (String codec : voiceRssCloud.getAvailableAudioCodecs()) { switch (codec) { case "MP3": audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_MP3, null, 16, 64000, @@ -271,15 +290,18 @@ public class VoiceRSSTTSService implements TTSService { * @throws TTSException if {@code format} is not supported */ private String getApiAudioFormat(AudioFormat format) throws TTSException { - final int bitDepth = format.getBitDepth() != null ? format.getBitDepth() : 16; - final Long frequency = format.getFrequency() != null ? format.getFrequency() : 44_100L; + final Integer formatBitDepth = format.getBitDepth(); + final int bitDepth = formatBitDepth != null ? formatBitDepth.intValue() : 16; + final Long formatFrequency = format.getFrequency(); + final Long frequency = formatFrequency != null ? formatFrequency.longValue() : 44_100L; final String apiFrequency = FREQUENCY_MAP.get(frequency); if (apiFrequency == null || (bitDepth != 8 && bitDepth != 16)) { throw new TTSException("Unsupported audio format: " + format); } - switch (format.getCodec() != null ? format.getCodec() : AudioFormat.CODEC_PCM_SIGNED) { + String codec = format.getCodec(); + switch (codec != null ? codec : AudioFormat.CODEC_PCM_SIGNED) { case AudioFormat.CODEC_PCM_ALAW: return "alaw_" + apiFrequency + "_mono"; case AudioFormat.CODEC_PCM_ULAW: @@ -310,7 +332,7 @@ public class VoiceRSSTTSService implements TTSService { } @Override - public String getLabel(Locale locale) { + public String getLabel(@Nullable Locale locale) { return "VoiceRSS"; } } diff --git a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/tool/CreateTTSCache.java b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/tool/CreateTTSCache.java index 6e9fed935..9c9214ebb 100644 --- a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/tool/CreateTTSCache.java +++ b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/tool/CreateTTSCache.java @@ -16,6 +16,7 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.io.PrintStream; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.voice.voicerss.internal.cloudapi.CachedVoiceRSSCloudImpl; @@ -73,7 +74,10 @@ public class CreateTTSCache { File inputFile = new File(inputFileName); if (!inputFile.exists()) { usage(); - System.err.println("File " + inputFileName + " not found"); + PrintStream printStream = System.err; + if (printStream != null) { + printStream.println("File " + inputFileName + " not found"); + } return RC_INPUT_FILE_NOT_FOUND; } generateCacheForFile(apiKey, cacheDir, locale, voice, codec, format, inputFileName); @@ -85,22 +89,26 @@ public class CreateTTSCache { } private void usage() { - System.out.println("Usage: java org.openhab.voice.voicerss.tool.CreateTTSCache "); - System.out.println( + PrintStream printStream = System.out; + if (printStream == null) { + return; + } + printStream.println("Usage: java org.openhab.voice.voicerss.tool.CreateTTSCache "); + printStream.println( "Arguments: --api-key { | @inputfile } [ ]"); - System.out.println(" key the VoiceRSS API Key, e.g. \"123456789\""); - System.out.println(" cache-dir is directory where the files will be stored, e.g. \"voicerss-cache\""); - System.out.println(" locale the language locale, has to be valid, e.g. \"en-us\", \"de-de\""); - System.out.println(" voice the voice, \"default\" for the default voice"); - System.out.println(" text the text to create audio file for, e.g. \"Hello World\""); - System.out.println( + printStream.println(" key the VoiceRSS API Key, e.g. \"123456789\""); + printStream.println(" cache-dir is directory where the files will be stored, e.g. \"voicerss-cache\""); + printStream.println(" locale the language locale, has to be valid, e.g. \"en-us\", \"de-de\""); + printStream.println(" voice the voice, \"default\" for the default voice"); + printStream.println(" text the text to create audio file for, e.g. \"Hello World\""); + printStream.println( " inputfile a name of a file, where all lines will be translatet to text, e.g. \"@message.txt\""); - System.out.println(" codec the audio codec, \"MP3\", \"WAV\", \"OGG\" or \"AAC\", \"MP3\" by default"); - System.out.println(" format the audio format, \"44khz_16bit_mono\" by default"); - System.out.println(); - System.out.println( + printStream.println(" codec the audio codec, \"MP3\", \"WAV\", \"OGG\" or \"AAC\", \"MP3\" by default"); + printStream.println(" format the audio format, \"44khz_16bit_mono\" by default"); + printStream.println(); + printStream.println( "Sample: java org.openhab.voice.voicerss.tool.CreateTTSCache --api-key 1234567890 cache en-US default @messages.txt"); - System.out.println(); + printStream.println(); } private void generateCacheForFile(String apiKey, String cacheDir, String locale, String voice, String codec, @@ -117,19 +125,29 @@ public class CreateTTSCache { private void generateCacheForMessage(String apiKey, String cacheDir, String locale, String voice, String codec, String format, String msg) throws IOException { + PrintStream printStream; String trimmedMsg = msg.trim(); if (trimmedMsg.length() == 0) { - System.err.println("Ignore msg=''"); + printStream = System.err; + if (printStream != null) { + printStream.println("Ignore msg=''"); + } return; } try { CachedVoiceRSSCloudImpl impl = new CachedVoiceRSSCloudImpl(cacheDir, false); File cachedFile = impl.getTextToSpeechAsFile(apiKey, trimmedMsg, locale, voice, codec, format); - System.out.println("Created cached audio for locale='" + locale + "', voice='" + voice + "', msg='" - + trimmedMsg + "' to file=" + cachedFile); + printStream = System.out; + if (printStream != null) { + printStream.println("Created cached audio for locale='" + locale + "', voice='" + voice + "', msg='" + + trimmedMsg + "' to file=" + cachedFile); + } } catch (IllegalStateException | IOException ex) { - System.err.println("Failed to create cached audio for locale='" + locale + "', voice='" + voice + "',msg='" - + trimmedMsg + "'"); + printStream = System.err; + if (printStream != null) { + printStream.println("Failed to create cached audio for locale='" + locale + "', voice='" + voice + + "',msg='" + trimmedMsg + "'"); + } } } }