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;
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;
* @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 {
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<Voice> voices;
+ private @Nullable Set<Voice> voices;
/**
* Set of supported audio formats
*/
- private Set<AudioFormat> audioFormats;
+ private @Nullable Set<AudioFormat> audioFormats;
/**
* DS activate, with access to ConfigAdmin
*/
- protected void activate(Map<String, Object> config) {
+ @Activate
+ protected void activate(@Nullable Map<String, Object> config) {
try {
modified(config);
voiceRssImpl = initVoiceImplementation();
}
@Modified
- protected void modified(Map<String, Object> config) {
+ protected void modified(@Nullable Map<String, Object> config) {
if (config != null) {
apiKey = config.containsKey(CONFIG_API_KEY) ? config.get(CONFIG_API_KEY).toString() : null;
}
@Override
public Set<Voice> getAvailableVoices() {
- return Collections.unmodifiableSet(voices);
+ Set<Voice> localVoices = voices;
+ return localVoices == null ? Set.of() : Collections.unmodifiableSet(localVoices);
}
@Override
public Set<AudioFormat> getSupportedFormats() {
- return Collections.unmodifiableSet(audioFormats);
+ Set<AudioFormat> 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);
+ CachedVoiceRSSCloudImpl voiceRssCloud = voiceRssImpl;
+ if (voiceRssCloud == null) {
+ throw new TTSException("The service is not correctly initialized");
+ }
// Validate known api key
- if (apiKey == null) {
+ String key = apiKey;
+ if (key == null) {
throw new TTSException("Missing API key, configure it first before using");
}
- // Validate arguments
- if (text == null) {
- throw new TTSException("The passed text is null");
- }
// trim text
String trimmedText = text.trim();
if (trimmedText.isEmpty()) {
throw new TTSException("The passed text is empty");
}
- if (!voices.contains(voice)) {
+ Set<Voice> 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);
* Initializes voices.
*
* @return The voices of this instance
+ * @throws IllegalStateException if voiceRssImpl is null
*/
- private Set<Voice> initVoices() {
+ private Set<Voice> initVoices() throws IllegalStateException {
+ CachedVoiceRSSCloudImpl voiceRssCloud = voiceRssImpl;
+ if (voiceRssCloud == null) {
+ throw new IllegalStateException("The service is not correctly initialized");
+ }
Set<Voice> 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));
}
}
* Initializes audioFormats
*
* @return The audio formats of this instance
+ * @throws IllegalStateException if voiceRssImpl is null
*/
- private Set<AudioFormat> initAudioFormats() {
+ private Set<AudioFormat> initAudioFormats() throws IllegalStateException {
+ CachedVoiceRSSCloudImpl voiceRssCloud = voiceRssImpl;
+ if (voiceRssCloud == null) {
+ throw new IllegalStateException("The service is not correctly initialized");
+ }
Set<AudioFormat> 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,
* @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:
}
@Override
- public String getLabel(Locale locale) {
+ public String getLabel(@Nullable Locale locale) {
return "VoiceRSS";
}
}
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;
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);
}
private void usage() {
- System.out.println("Usage: java org.openhab.voice.voicerss.tool.CreateTTSCache <args>");
- System.out.println(
+ PrintStream printStream = System.out;
+ if (printStream == null) {
+ return;
+ }
+ printStream.println("Usage: java org.openhab.voice.voicerss.tool.CreateTTSCache <args>");
+ printStream.println(
"Arguments: --api-key <key> <cache-dir> <locale> <voice> { <text> | @inputfile } [ <codec> <format> ]");
- 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,
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 + "'");
+ }
}
}
}