[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 <lg.hc@free.fr>
This commit is contained in:
lolodomo 2022-02-02 18:39:35 +01:00 committed by GitHub
parent de6de1a22d
commit 624efab678
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 170 additions and 79 deletions

View File

@ -177,17 +177,17 @@ Synopsis of this tool:
``` ```
Usage: java org.openhab.voice.voicerss.tool.CreateTTSCache <args> Usage: java org.openhab.voice.voicerss.tool.CreateTTSCache <args>
Arguments: --api-key <key> <cache-dir> <locale> { <text> | @inputfile } Arguments: --api-key <key> <cache-dir> <locale> <voice> { <text> | @inputfile } [ <codec> <format> ]
key the VoiceRSS API Key, e.g. "123456789" key the VoiceRSS API Key, e.g. "123456789"
cache-dir is directory where the files will be stored, e.g. "voicerss-cache" 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" 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" 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" 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
``` ```
You will need to specify the classpath for your addon (jar) in the command line (java -cp <path> ...).
## Open Issues
* do not log API-Key in plain text

View File

@ -14,6 +14,7 @@ package org.openhab.voice.voicerss.internal;
import java.io.File; import java.io.File;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.audio.AudioException; import org.openhab.core.audio.AudioException;
import org.openhab.core.audio.AudioFormat; import org.openhab.core.audio.AudioFormat;
import org.openhab.core.audio.AudioStream; import org.openhab.core.audio.AudioStream;
@ -27,6 +28,7 @@ import org.openhab.core.audio.FileAudioStream;
* *
* @author Jochen Hiller - Initial contribution and API * @author Jochen Hiller - Initial contribution and API
*/ */
@NonNullByDefault
class VoiceRSSAudioStream extends FileAudioStream { class VoiceRSSAudioStream extends FileAudioStream {
public VoiceRSSAudioStream(File audioFile, AudioFormat format) throws AudioException { public VoiceRSSAudioStream(File audioFile, AudioFormat format) throws AudioException {

View File

@ -170,7 +170,82 @@ public class VoiceRSSTTSService implements TTSService {
* @return The audio formats of this instance * @return The audio formats of this instance
*/ */
private Set<AudioFormat> initAudioFormats() { private Set<AudioFormat> initAudioFormats() {
return voiceRssImpl.getAvailableAudioFormats(); Set<AudioFormat> 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 { private CachedVoiceRSSCloudImpl initVoiceImplementation() throws IllegalStateException {
return new CachedVoiceRSSCloudImpl(getCacheFolderName()); return new CachedVoiceRSSCloudImpl(getCacheFolderName(), true);
} }
private String getCacheFolderName() { private String getCacheFolderName() {

View File

@ -14,6 +14,7 @@ package org.openhab.voice.voicerss.internal;
import java.util.Locale; import java.util.Locale;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.voice.Voice; import org.openhab.core.voice.Voice;
import org.openhab.voice.voicerss.internal.cloudapi.VoiceRSSCloudImpl; 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 * @author Jochen Hiller - Initial contribution and API
*/ */
@NonNullByDefault
public class VoiceRSSVoice implements Voice { public class VoiceRSSVoice implements Voice {
/** /**

View File

@ -23,7 +23,8 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Objects; import java.util.Objects;
import org.slf4j.Logger; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
@ -33,6 +34,7 @@ import org.slf4j.LoggerFactory;
* *
* @author Jochen Hiller - Initial contribution * @author Jochen Hiller - Initial contribution
*/ */
@NonNullByDefault
public class CachedVoiceRSSCloudImpl extends VoiceRSSCloudImpl { public class CachedVoiceRSSCloudImpl extends VoiceRSSCloudImpl {
/** /**
@ -40,12 +42,11 @@ public class CachedVoiceRSSCloudImpl extends VoiceRSSCloudImpl {
*/ */
private static final int READ_BUFFER_SIZE = 4096; private static final int READ_BUFFER_SIZE = 4096;
private final Logger logger = LoggerFactory.getLogger(CachedVoiceRSSCloudImpl.class);
private final File cacheFolder; private final File cacheFolder;
public CachedVoiceRSSCloudImpl(String cacheFolderName) throws IllegalStateException { public CachedVoiceRSSCloudImpl(String cacheFolderName, boolean logging) throws IllegalStateException {
if (cacheFolderName == null) { super(logging);
if (cacheFolderName.isBlank()) {
throw new IllegalStateException("Folder for cache must be defined"); throw new IllegalStateException("Folder for cache must be defined");
} }
// Lazy create the cache folder // Lazy create the cache folder
@ -89,7 +90,7 @@ public class CachedVoiceRSSCloudImpl extends VoiceRSSCloudImpl {
* *
* Sample: "en-US_00a2653ac5f77063bc4ea2fee87318d3" * 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 { try {
byte[] bytesOfMessage = text.getBytes(StandardCharsets.UTF_8); byte[] bytesOfMessage = text.getBytes(StandardCharsets.UTF_8);
MessageDigest md = MessageDigest.getInstance("MD5"); MessageDigest md = MessageDigest.getInstance("MD5");
@ -112,7 +113,10 @@ public class CachedVoiceRSSCloudImpl extends VoiceRSSCloudImpl {
return filename; return filename;
} catch (NoSuchAlgorithmException ex) { } catch (NoSuchAlgorithmException ex) {
// should not happen // 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; return null;
} }
} }

View File

@ -17,7 +17,7 @@ import java.io.InputStream;
import java.util.Locale; import java.util.Locale;
import java.util.Set; 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 * 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 * @author Jochen Hiller - Initial contribution
*/ */
@NonNullByDefault
public interface VoiceRSSCloudAPI { public interface VoiceRSSCloudAPI {
/** /**
@ -35,13 +36,12 @@ public interface VoiceRSSCloudAPI {
Set<Locale> getAvailableLocales(); Set<Locale> getAvailableLocales();
/** /**
* Get all supported audio formats by the TTS service. This includes MP3, * Get all supported audio codecs by the TTS service. This includes MP3,
* WAV and more audio formats as used in APIs. About supported audio * WAV and more audio formats as used in APIs.
* formats, see {@link AudioFormat}
* *
* @return A set of all audio formats supported * @return A set of all audio codecs supported
*/ */
Set<AudioFormat> getAvailableAudioFormats(); Set<String> getAvailableAudioCodecs();
/** /**
* Get all supported voices. * Get all supported voices.

View File

@ -27,7 +27,7 @@ import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import org.openhab.core.audio.AudioFormat; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -48,6 +48,7 @@ import org.slf4j.LoggerFactory;
* @author Laurent Garnier - add support for OGG and AAC audio formats * @author Laurent Garnier - add support for OGG and AAC audio formats
* @author Andreas Brenk - add support for WAV audio format * @author Andreas Brenk - add support for WAV audio format
*/ */
@NonNullByDefault
public class VoiceRSSCloudImpl implements VoiceRSSCloudAPI { public class VoiceRSSCloudImpl implements VoiceRSSCloudAPI {
public static final String DEFAULT_VOICE = "default"; 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 = "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"; public static final String API_URL_WITH_VOICE = API_URL + "&v=%s";
private static final Set<AudioFormat> SUPPORTED_AUDIO_FORMATS = Set.of( private static final Set<String> SUPPORTED_AUDIO_CODECS = Set.of("MP3", "OGG", "AAC", "WAV", "CAF");
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<Locale> SUPPORTED_LOCALES = new HashSet<>(); private static final Set<Locale> SUPPORTED_LOCALES = new HashSet<>();
static { static {
@ -192,11 +164,15 @@ public class VoiceRSSCloudImpl implements VoiceRSSCloudAPI {
SUPPORTED_VOICES.put("zh-tw", Set.of("Akemi", "Lin", "Lee")); 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 @Override
public Set<AudioFormat> getAvailableAudioFormats() { public Set<String> getAvailableAudioCodecs() {
return SUPPORTED_AUDIO_FORMATS; return SUPPORTED_AUDIO_CODECS;
} }
@Override @Override
@ -242,7 +218,9 @@ public class VoiceRSSCloudImpl implements VoiceRSSCloudAPI {
public InputStream getTextToSpeech(String apiKey, String text, String locale, String voice, String audioCodec, public InputStream getTextToSpeech(String apiKey, String text, String locale, String voice, String audioCodec,
String audioFormat) throws IOException { String audioFormat) throws IOException {
String url = createURL(apiKey, text, locale, voice, audioCodec, audioFormat); 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(); URLConnection connection = new URL(url).openConnection();
// we will check return codes. The service will ALWAYS return a HTTP // we will check return codes. The service will ALWAYS return a HTTP
@ -250,14 +228,20 @@ public class VoiceRSSCloudImpl implements VoiceRSSCloudAPI {
// the error message in body // the error message in body
int status = ((HttpURLConnection) connection).getResponseCode(); int status = ((HttpURLConnection) connection).getResponseCode();
if (HttpURLConnection.HTTP_OK != status) { 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); throw new IOException("Could not read from service: HTTP code " + status);
} }
if (logging) {
Logger logger = LoggerFactory.getLogger(VoiceRSSCloudImpl.class);
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) { for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
logger.trace("Response.header: {}={}", header.getKey(), header.getValue()); logger.trace("Response.header: {}={}", header.getKey(), header.getValue());
} }
} }
}
String contentType = connection.getHeaderField("Content-Type"); String contentType = connection.getHeaderField("Content-Type");
InputStream is = connection.getInputStream(); InputStream is = connection.getInputStream();
// check if content type is text/plain, then we have an error // check if content type is text/plain, then we have an error
@ -268,7 +252,9 @@ public class VoiceRSSCloudImpl implements VoiceRSSCloudAPI {
try { try {
is.close(); is.close();
} catch (IOException ex) { } 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( throw new IOException(
"Could not read audio content, service returned an error: " + new String(bytes, "UTF-8")); "Could not read audio content, service returned an error: " + new String(bytes, "UTF-8"));

View File

@ -17,6 +17,7 @@ import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.voice.voicerss.internal.cloudapi.CachedVoiceRSSCloudImpl; 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 * @author Jochen Hiller - Initial contribution
*/ */
@NonNullByDefault
public class CreateTTSCache { public class CreateTTSCache {
public static final int RC_OK = 0; public static final int RC_OK = 0;
public static final int RC_USAGE = 1; public static final int RC_USAGE = 1;
public static final int RC_INPUT_FILE_NOT_FOUND = 2; public static final int RC_INPUT_FILE_NOT_FOUND = 2;
public static final int RC_API_KEY_MISSING = 3; 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 { public static void main(String[] args) throws IOException {
CreateTTSCache tool = new CreateTTSCache(); CreateTTSCache tool = new CreateTTSCache();
@ -38,7 +41,7 @@ public class CreateTTSCache {
} }
public int doMain(String[] args) throws IOException { public int doMain(String[] args) throws IOException {
if ((args == null) || (args.length != 5)) { if (args.length < 6) {
usage(); usage();
return RC_USAGE; return RC_USAGE;
} }
@ -50,6 +53,21 @@ public class CreateTTSCache {
String cacheDir = args[2]; String cacheDir = args[2];
String locale = args[3]; String locale = args[3];
String voice = args[4]; 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("@")) { if (args[5].startsWith("@")) {
String inputFileName = args[5].substring(1); String inputFileName = args[5].substring(1);
File inputFile = new File(inputFileName); File inputFile = new File(inputFileName);
@ -58,17 +76,18 @@ public class CreateTTSCache {
System.err.println("File " + inputFileName + " not found"); System.err.println("File " + inputFileName + " not found");
return RC_INPUT_FILE_NOT_FOUND; return RC_INPUT_FILE_NOT_FOUND;
} }
generateCacheForFile(apiKey, cacheDir, locale, voice, inputFileName); generateCacheForFile(apiKey, cacheDir, locale, voice, codec, format, inputFileName);
} else { } else {
String text = args[5]; String text = args[5];
generateCacheForMessage(apiKey, cacheDir, locale, voice, text); generateCacheForMessage(apiKey, cacheDir, locale, voice, codec, format, text);
} }
return RC_OK; return RC_OK;
} }
private void usage() { private void usage() {
System.out.println("Usage: java org.openhab.voice.voicerss.tool.CreateTTSCache <args>"); System.out.println("Usage: java org.openhab.voice.voicerss.tool.CreateTTSCache <args>");
System.out.println("Arguments: --api-key <key> <cache-dir> <locale> { <text> | @inputfile }"); System.out.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(" 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(" 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(" 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(" text the text to create audio file for, e.g. \"Hello World\"");
System.out.println( System.out.println(
" inputfile a name of a file, where all lines will be translatet to text, e.g. \"@message.txt\""); " 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();
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(); System.out.println();
} }
private void generateCacheForFile(String apiKey, String cacheDir, String locale, String voice, String inputFileName) private void generateCacheForFile(String apiKey, String cacheDir, String locale, String voice, String codec,
throws IOException { String format, String inputFileName) throws IOException {
File inputFile = new File(inputFileName); File inputFile = new File(inputFileName);
try (BufferedReader br = new BufferedReader(new FileReader(inputFile))) { try (BufferedReader br = new BufferedReader(new FileReader(inputFile))) {
String line; String line;
while ((line = br.readLine()) != null) { while ((line = br.readLine()) != null) {
// process the line. // 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) private void generateCacheForMessage(String apiKey, String cacheDir, String locale, String voice, String codec,
throws IOException { String format, String msg) throws IOException {
if (msg == null) {
System.err.println("Ignore msg=null");
return;
}
String trimmedMsg = msg.trim(); String trimmedMsg = msg.trim();
if (trimmedMsg.length() == 0) { if (trimmedMsg.length() == 0) {
System.err.println("Ignore msg=''"); System.err.println("Ignore msg=''");
return; return;
} }
CachedVoiceRSSCloudImpl impl = new CachedVoiceRSSCloudImpl(cacheDir); try {
File cachedFile = impl.getTextToSpeechAsFile(apiKey, trimmedMsg, locale, voice, "MP3", null); CachedVoiceRSSCloudImpl impl = new CachedVoiceRSSCloudImpl(cacheDir, false);
System.out.println( File cachedFile = impl.getTextToSpeechAsFile(apiKey, trimmedMsg, locale, voice, codec, format);
"Created cached audio for locale='" + locale + "', msg='" + trimmedMsg + "' to file=" + cachedFile); 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 + "'");
}
} }
} }