From 624efab678219c8e57c7741c9c7c575994336ea5 Mon Sep 17 00:00:00 2001 From: lolodomo Date: Wed, 2 Feb 2022 18:39:35 +0100 Subject: [PATCH] [voicerss] Fix and enhance the external tool to prefill the cache (#12155) * [voicerss] Fix and enhance the external tool to prefill the cache * Make the tool independent of core openHAB * Fix the parsing of the command arguments. * Allow to provide optional audio codec and format. * Null annotations added on most of the classes * Simplified doc about classpath * Suppress obsolete mention of open issue in doc Signed-off-by: Laurent Garnier --- bundles/org.openhab.voice.voicerss/README.md | 12 +-- .../internal/VoiceRSSAudioStream.java | 2 + .../voicerss/internal/VoiceRSSTTSService.java | 79 ++++++++++++++++++- .../voicerss/internal/VoiceRSSVoice.java | 2 + .../cloudapi/CachedVoiceRSSCloudImpl.java | 18 +++-- .../internal/cloudapi/VoiceRSSCloudAPI.java | 12 +-- .../internal/cloudapi/VoiceRSSCloudImpl.java | 66 ++++++---------- .../voice/voicerss/tool/CreateTTSCache.java | 58 +++++++++----- 8 files changed, 170 insertions(+), 79 deletions(-) diff --git a/bundles/org.openhab.voice.voicerss/README.md b/bundles/org.openhab.voice.voicerss/README.md index 7cca6bc40..5d8d90845 100644 --- a/bundles/org.openhab.voice.voicerss/README.md +++ b/bundles/org.openhab.voice.voicerss/README.md @@ -177,17 +177,17 @@ Synopsis of this tool: ``` Usage: java org.openhab.voice.voicerss.tool.CreateTTSCache -Arguments: --api-key { | @inputfile } +Arguments: --api-key { | @inputfile } [ ] key the VoiceRSS API Key, e.g. "123456789" cache-dir is directory where the files will be stored, e.g. "voicerss-cache" locale the language locale, has to be valid, e.g. "en-us", "de-de" + voice the voice, "default" for the default voice text the text to create audio file for, e.g. "Hello World" inputfile a name of a file, where all lines will be translatet to text, e.g. "@message.txt" + codec the audio codec, "MP3", "WAV", "OGG" or "AAC", "MP3" by default + format the audio format, "44khz_16bit_mono" by default -Sample: java org.openhab.voice.voicerss.tool.CreateTTSCache --api-key 1234567890 cache en-US @messages.txt +Sample: java org.openhab.voice.voicerss.tool.CreateTTSCache --api-key 1234567890 cache en-US default @messages.txt ``` - -## Open Issues - -* do not log API-Key in plain text +You will need to specify the classpath for your addon (jar) in the command line (java -cp ...). diff --git a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSAudioStream.java b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSAudioStream.java index 8657c5805..787a0312f 100644 --- a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSAudioStream.java +++ b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSAudioStream.java @@ -14,6 +14,7 @@ package org.openhab.voice.voicerss.internal; import java.io.File; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.audio.AudioException; import org.openhab.core.audio.AudioFormat; import org.openhab.core.audio.AudioStream; @@ -27,6 +28,7 @@ import org.openhab.core.audio.FileAudioStream; * * @author Jochen Hiller - Initial contribution and API */ +@NonNullByDefault class VoiceRSSAudioStream extends FileAudioStream { public VoiceRSSAudioStream(File audioFile, AudioFormat format) throws AudioException { 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 2fdae5808..73eecbec8 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 @@ -170,7 +170,82 @@ public class VoiceRSSTTSService implements TTSService { * @return The audio formats of this instance */ private Set initAudioFormats() { - return voiceRssImpl.getAvailableAudioFormats(); + Set audioFormats = new HashSet<>(); + for (String codec : voiceRssImpl.getAvailableAudioCodecs()) { + switch (codec) { + case "MP3": + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_MP3, null, 16, 64000, + 44_100L)); + break; + case "OGG": + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_OGG, AudioFormat.CODEC_VORBIS, null, 16, + null, 44_100L)); + break; + case "AAC": + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_AAC, null, 16, null, + 44_100L)); + break; + case "WAV": + // Consider only mono formats + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, + 8, 64_000, 8_000L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, + 16, 128_000, 8_000L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, + 8, 88_200, 11_025L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, + 16, 176_400, 11_025L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, + 8, 96_000, 12_000L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, + 16, 192_000, 12_000L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, + 8, 128_000, 16_000L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, + 16, 256_000, 16_000L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, + 8, 176_400, 22_050L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, + 16, 352_800, 22_050L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, + 8, 192_000, 24_000L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, + 16, 384_000, 24_000L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, + 8, 256_000, 32_000L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, + 16, 512_000, 32_000L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, + 8, 352_800, 44_100L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, + 16, 705_600, 44_100L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, + 8, 384_000, 48_000L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, + 16, 768_000, 48_000L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ALAW, null, 8, + 64_000, 8_000L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ALAW, null, 8, + 88_200, 11_025L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ALAW, null, 8, + 176_400, 22_050L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ALAW, null, 8, + 352_800, 44_100L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ULAW, null, 8, + 64_000, 8_000L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ULAW, null, 8, + 88_200, 11_025L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ULAW, null, 8, + 176_400, 22_050L)); + audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ULAW, null, 8, + 352_800, 44_100L)); + break; + default: + logger.debug("Audio codec {} not yet supported", codec); + break; + } + } + return audioFormats; } /** @@ -221,7 +296,7 @@ public class VoiceRSSTTSService implements TTSService { } private CachedVoiceRSSCloudImpl initVoiceImplementation() throws IllegalStateException { - return new CachedVoiceRSSCloudImpl(getCacheFolderName()); + return new CachedVoiceRSSCloudImpl(getCacheFolderName(), true); } private String getCacheFolderName() { diff --git a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSVoice.java b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSVoice.java index 4b2c4b0af..6a752f70d 100644 --- a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSVoice.java +++ b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSVoice.java @@ -14,6 +14,7 @@ package org.openhab.voice.voicerss.internal; import java.util.Locale; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.voice.Voice; import org.openhab.voice.voicerss.internal.cloudapi.VoiceRSSCloudImpl; @@ -23,6 +24,7 @@ import org.openhab.voice.voicerss.internal.cloudapi.VoiceRSSCloudImpl; * * @author Jochen Hiller - Initial contribution and API */ +@NonNullByDefault public class VoiceRSSVoice implements Voice { /** diff --git a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/CachedVoiceRSSCloudImpl.java b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/CachedVoiceRSSCloudImpl.java index fd4977f2f..ba6f8fdb8 100644 --- a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/CachedVoiceRSSCloudImpl.java +++ b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/CachedVoiceRSSCloudImpl.java @@ -23,7 +23,8 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Objects; -import org.slf4j.Logger; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.slf4j.LoggerFactory; /** @@ -33,6 +34,7 @@ import org.slf4j.LoggerFactory; * * @author Jochen Hiller - Initial contribution */ +@NonNullByDefault public class CachedVoiceRSSCloudImpl extends VoiceRSSCloudImpl { /** @@ -40,12 +42,11 @@ public class CachedVoiceRSSCloudImpl extends VoiceRSSCloudImpl { */ private static final int READ_BUFFER_SIZE = 4096; - private final Logger logger = LoggerFactory.getLogger(CachedVoiceRSSCloudImpl.class); - private final File cacheFolder; - public CachedVoiceRSSCloudImpl(String cacheFolderName) throws IllegalStateException { - if (cacheFolderName == null) { + public CachedVoiceRSSCloudImpl(String cacheFolderName, boolean logging) throws IllegalStateException { + super(logging); + if (cacheFolderName.isBlank()) { throw new IllegalStateException("Folder for cache must be defined"); } // Lazy create the cache folder @@ -89,7 +90,7 @@ public class CachedVoiceRSSCloudImpl extends VoiceRSSCloudImpl { * * Sample: "en-US_00a2653ac5f77063bc4ea2fee87318d3" */ - private String getUniqueFilenameForText(String text, String locale, String voice, String format) { + private @Nullable String getUniqueFilenameForText(String text, String locale, String voice, String format) { try { byte[] bytesOfMessage = text.getBytes(StandardCharsets.UTF_8); MessageDigest md = MessageDigest.getInstance("MD5"); @@ -112,7 +113,10 @@ public class CachedVoiceRSSCloudImpl extends VoiceRSSCloudImpl { return filename; } catch (NoSuchAlgorithmException ex) { // should not happen - logger.error("Could not create MD5 hash for '{}'", text, ex); + if (logging) { + LoggerFactory.getLogger(CachedVoiceRSSCloudImpl.class).error("Could not create MD5 hash for '{}'", text, + ex); + } return null; } } diff --git a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudAPI.java b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudAPI.java index 27fb29290..84ec44ffc 100644 --- a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudAPI.java +++ b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudAPI.java @@ -17,7 +17,7 @@ import java.io.InputStream; import java.util.Locale; import java.util.Set; -import org.openhab.core.audio.AudioFormat; +import org.eclipse.jdt.annotation.NonNullByDefault; /** * Interface which represents the functionality needed from the VoiceRSS TTS @@ -25,6 +25,7 @@ import org.openhab.core.audio.AudioFormat; * * @author Jochen Hiller - Initial contribution */ +@NonNullByDefault public interface VoiceRSSCloudAPI { /** @@ -35,13 +36,12 @@ public interface VoiceRSSCloudAPI { Set getAvailableLocales(); /** - * Get all supported audio formats by the TTS service. This includes MP3, - * WAV and more audio formats as used in APIs. About supported audio - * formats, see {@link AudioFormat} + * Get all supported audio codecs by the TTS service. This includes MP3, + * WAV and more audio formats as used in APIs. * - * @return A set of all audio formats supported + * @return A set of all audio codecs supported */ - Set getAvailableAudioFormats(); + Set getAvailableAudioCodecs(); /** * Get all supported voices. diff --git a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudImpl.java b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudImpl.java index 0c6f8c372..cb6bd5f2d 100644 --- a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudImpl.java +++ b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudImpl.java @@ -27,7 +27,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import org.openhab.core.audio.AudioFormat; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,6 +48,7 @@ import org.slf4j.LoggerFactory; * @author Laurent Garnier - add support for OGG and AAC audio formats * @author Andreas Brenk - add support for WAV audio format */ +@NonNullByDefault public class VoiceRSSCloudImpl implements VoiceRSSCloudAPI { public static final String DEFAULT_VOICE = "default"; @@ -55,36 +56,7 @@ public class VoiceRSSCloudImpl implements VoiceRSSCloudAPI { public static final String API_URL = "https://api.voicerss.org/?key=%s&hl=%s&c=%s&f=%s&src=%s"; public static final String API_URL_WITH_VOICE = API_URL + "&v=%s"; - private static final Set SUPPORTED_AUDIO_FORMATS = Set.of( - new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_MP3, null, 16, null, 44_100L), - new AudioFormat(AudioFormat.CONTAINER_OGG, AudioFormat.CODEC_VORBIS, null, 16, null, 44_100L), - new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_AAC, null, 16, null, 44_100L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, null, 8, 64_000, 8_000L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, null, 16, 128_000, 8_000L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 88_200, 11_025L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 176_400, 11_025L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 96_000, 12_000L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 192_000, 12_000L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 128_000, 16_000L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 256_000, 16_000L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 176_400, 22_050L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 352_800, 22_050L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 192_000, 24_000L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 384_000, 24_000L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 256_000, 32_000L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 512_000, 32_000L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 352_800, 44_100L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 705_600, 44_100L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 384_000, 48_000L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 768_000, 48_000L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ALAW, null, 8, 64_000, 8_000L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ALAW, null, 8, 88_200, 11_025L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ALAW, null, 8, 176_400, 22_050L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ALAW, null, 8, 352_800, 44_100L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ULAW, null, 8, 64_000, 8_000L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ULAW, null, 8, 88_200, 11_025L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ULAW, null, 8, 176_400, 22_050L), - new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ULAW, null, 8, 352_800, 44_100L)); + private static final Set SUPPORTED_AUDIO_CODECS = Set.of("MP3", "OGG", "AAC", "WAV", "CAF"); private static final Set SUPPORTED_LOCALES = new HashSet<>(); static { @@ -192,11 +164,15 @@ public class VoiceRSSCloudImpl implements VoiceRSSCloudAPI { SUPPORTED_VOICES.put("zh-tw", Set.of("Akemi", "Lin", "Lee")); } - private final Logger logger = LoggerFactory.getLogger(VoiceRSSCloudImpl.class); + protected boolean logging; + + public VoiceRSSCloudImpl(boolean logging) { + this.logging = logging; + } @Override - public Set getAvailableAudioFormats() { - return SUPPORTED_AUDIO_FORMATS; + public Set getAvailableAudioCodecs() { + return SUPPORTED_AUDIO_CODECS; } @Override @@ -242,7 +218,9 @@ public class VoiceRSSCloudImpl implements VoiceRSSCloudAPI { public InputStream getTextToSpeech(String apiKey, String text, String locale, String voice, String audioCodec, String audioFormat) throws IOException { String url = createURL(apiKey, text, locale, voice, audioCodec, audioFormat); - logger.debug("Call {}", url.replace(apiKey, "***")); + if (logging) { + LoggerFactory.getLogger(VoiceRSSCloudImpl.class).debug("Call {}", url.replace(apiKey, "***")); + } URLConnection connection = new URL(url).openConnection(); // we will check return codes. The service will ALWAYS return a HTTP @@ -250,12 +228,18 @@ public class VoiceRSSCloudImpl implements VoiceRSSCloudAPI { // the error message in body int status = ((HttpURLConnection) connection).getResponseCode(); if (HttpURLConnection.HTTP_OK != status) { - logger.warn("Call {} returned HTTP {}", url.replace(apiKey, "***"), status); + if (logging) { + LoggerFactory.getLogger(VoiceRSSCloudImpl.class).warn("Call {} returned HTTP {}", + url.replace(apiKey, "***"), status); + } throw new IOException("Could not read from service: HTTP code " + status); } - if (logger.isTraceEnabled()) { - for (Entry> header : connection.getHeaderFields().entrySet()) { - logger.trace("Response.header: {}={}", header.getKey(), header.getValue()); + if (logging) { + Logger logger = LoggerFactory.getLogger(VoiceRSSCloudImpl.class); + if (logger.isTraceEnabled()) { + for (Entry> header : connection.getHeaderFields().entrySet()) { + logger.trace("Response.header: {}={}", header.getKey(), header.getValue()); + } } } String contentType = connection.getHeaderField("Content-Type"); @@ -268,7 +252,9 @@ public class VoiceRSSCloudImpl implements VoiceRSSCloudAPI { try { is.close(); } catch (IOException ex) { - logger.debug("Failed to close inputstream", ex); + if (logging) { + LoggerFactory.getLogger(VoiceRSSCloudImpl.class).debug("Failed to close inputstream", ex); + } } throw new IOException( "Could not read audio content, service returned an error: " + new String(bytes, "UTF-8")); 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 f5d1c1ae6..6e9fed935 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 @@ -17,6 +17,7 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.voice.voicerss.internal.cloudapi.CachedVoiceRSSCloudImpl; /** @@ -24,12 +25,14 @@ import org.openhab.voice.voicerss.internal.cloudapi.CachedVoiceRSSCloudImpl; * * @author Jochen Hiller - Initial contribution */ +@NonNullByDefault public class CreateTTSCache { public static final int RC_OK = 0; public static final int RC_USAGE = 1; public static final int RC_INPUT_FILE_NOT_FOUND = 2; public static final int RC_API_KEY_MISSING = 3; + public static final int RC_INVALID_CODEC = 4; public static void main(String[] args) throws IOException { CreateTTSCache tool = new CreateTTSCache(); @@ -38,7 +41,7 @@ public class CreateTTSCache { } public int doMain(String[] args) throws IOException { - if ((args == null) || (args.length != 5)) { + if (args.length < 6) { usage(); return RC_USAGE; } @@ -50,6 +53,21 @@ public class CreateTTSCache { String cacheDir = args[2]; String locale = args[3]; String voice = args[4]; + String codec = "MP3"; + if (args.length >= 7) { + switch (args[6]) { + case "MP3": + case "WAV": + case "OGG": + case "AAC": + codec = args[6]; + break; + default: + usage(); + return RC_INVALID_CODEC; + } + } + String format = args.length >= 8 ? args[7] : "44khz_16bit_mono"; if (args[5].startsWith("@")) { String inputFileName = args[5].substring(1); File inputFile = new File(inputFileName); @@ -58,17 +76,18 @@ public class CreateTTSCache { System.err.println("File " + inputFileName + " not found"); return RC_INPUT_FILE_NOT_FOUND; } - generateCacheForFile(apiKey, cacheDir, locale, voice, inputFileName); + generateCacheForFile(apiKey, cacheDir, locale, voice, codec, format, inputFileName); } else { String text = args[5]; - generateCacheForMessage(apiKey, cacheDir, locale, voice, text); + generateCacheForMessage(apiKey, cacheDir, locale, voice, codec, format, text); } return RC_OK; } private void usage() { System.out.println("Usage: java org.openhab.voice.voicerss.tool.CreateTTSCache "); - System.out.println("Arguments: --api-key { | @inputfile }"); + System.out.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\""); @@ -76,38 +95,41 @@ public class CreateTTSCache { System.out.println(" text the text to create audio file for, e.g. \"Hello World\""); System.out.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( - "Sample: java org.openhab.voice.voicerss.tool.CreateTTSCache --api-key 1234567890 cache en-US @messages.txt"); + "Sample: java org.openhab.voice.voicerss.tool.CreateTTSCache --api-key 1234567890 cache en-US default @messages.txt"); System.out.println(); } - private void generateCacheForFile(String apiKey, String cacheDir, String locale, String voice, String inputFileName) - throws IOException { + private void generateCacheForFile(String apiKey, String cacheDir, String locale, String voice, String codec, + String format, String inputFileName) throws IOException { File inputFile = new File(inputFileName); try (BufferedReader br = new BufferedReader(new FileReader(inputFile))) { String line; while ((line = br.readLine()) != null) { // process the line. - generateCacheForMessage(apiKey, cacheDir, locale, voice, line); + generateCacheForMessage(apiKey, cacheDir, locale, voice, codec, format, line); } } } - private void generateCacheForMessage(String apiKey, String cacheDir, String locale, String voice, String msg) - throws IOException { - if (msg == null) { - System.err.println("Ignore msg=null"); - return; - } + private void generateCacheForMessage(String apiKey, String cacheDir, String locale, String voice, String codec, + String format, String msg) throws IOException { String trimmedMsg = msg.trim(); if (trimmedMsg.length() == 0) { System.err.println("Ignore msg=''"); return; } - CachedVoiceRSSCloudImpl impl = new CachedVoiceRSSCloudImpl(cacheDir); - File cachedFile = impl.getTextToSpeechAsFile(apiKey, trimmedMsg, locale, voice, "MP3", null); - System.out.println( - "Created cached audio for locale='" + locale + "', msg='" + trimmedMsg + "' to file=" + cachedFile); + 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); + } catch (IllegalStateException | IOException ex) { + System.err.println("Failed to create cached audio for locale='" + locale + "', voice='" + voice + "',msg='" + + trimmedMsg + "'"); + } } }