mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-27 07:41:39 +01:00
[voicerss] Null annotations added on the main service class (#12190)
Signed-off-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
parent
3c7d4d8a38
commit
02776d8585
@ -20,6 +20,8 @@ import java.util.Locale;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
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.OpenHAB;
|
||||||
import org.openhab.core.audio.AudioException;
|
import org.openhab.core.audio.AudioException;
|
||||||
import org.openhab.core.audio.AudioFormat;
|
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.core.voice.Voice;
|
||||||
import org.openhab.voice.voicerss.internal.cloudapi.CachedVoiceRSSCloudImpl;
|
import org.openhab.voice.voicerss.internal.cloudapi.CachedVoiceRSSCloudImpl;
|
||||||
import org.osgi.framework.Constants;
|
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.Component;
|
||||||
import org.osgi.service.component.annotations.Modified;
|
import org.osgi.service.component.annotations.Modified;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -41,6 +44,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
* @author Jochen Hiller - Initial contribution and API
|
* @author Jochen Hiller - Initial contribution and API
|
||||||
* @author Laurent Garnier - add support for OGG and AAC audio formats
|
* @author Laurent Garnier - add support for OGG and AAC audio formats
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
@Component(configurationPid = "org.openhab.voicerss", property = Constants.SERVICE_PID + "=org.openhab.voicerss")
|
@Component(configurationPid = "org.openhab.voicerss", property = Constants.SERVICE_PID + "=org.openhab.voicerss")
|
||||||
@ConfigurableService(category = "voice", label = "VoiceRSS Text-to-Speech", description_uri = "voice:voicerss")
|
@ConfigurableService(category = "voice", label = "VoiceRSS Text-to-Speech", description_uri = "voice:voicerss")
|
||||||
public class VoiceRSSTTSService implements TTSService {
|
public class VoiceRSSTTSService implements TTSService {
|
||||||
@ -66,27 +70,28 @@ public class VoiceRSSTTSService implements TTSService {
|
|||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(VoiceRSSTTSService.class);
|
private final Logger logger = LoggerFactory.getLogger(VoiceRSSTTSService.class);
|
||||||
|
|
||||||
private String apiKey;
|
private @Nullable String apiKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We need the cached implementation to allow for FixedLengthAudioStream.
|
* We need the cached implementation to allow for FixedLengthAudioStream.
|
||||||
*/
|
*/
|
||||||
private CachedVoiceRSSCloudImpl voiceRssImpl;
|
private @Nullable CachedVoiceRSSCloudImpl voiceRssImpl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set of supported voices
|
* Set of supported voices
|
||||||
*/
|
*/
|
||||||
private Set<Voice> voices;
|
private @Nullable Set<Voice> voices;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set of supported audio formats
|
* Set of supported audio formats
|
||||||
*/
|
*/
|
||||||
private Set<AudioFormat> audioFormats;
|
private @Nullable Set<AudioFormat> audioFormats;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DS activate, with access to ConfigAdmin
|
* DS activate, with access to ConfigAdmin
|
||||||
*/
|
*/
|
||||||
protected void activate(Map<String, Object> config) {
|
@Activate
|
||||||
|
protected void activate(@Nullable Map<String, Object> config) {
|
||||||
try {
|
try {
|
||||||
modified(config);
|
modified(config);
|
||||||
voiceRssImpl = initVoiceImplementation();
|
voiceRssImpl = initVoiceImplementation();
|
||||||
@ -100,7 +105,7 @@ public class VoiceRSSTTSService implements TTSService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Modified
|
@Modified
|
||||||
protected void modified(Map<String, Object> config) {
|
protected void modified(@Nullable Map<String, Object> config) {
|
||||||
if (config != null) {
|
if (config != null) {
|
||||||
apiKey = config.containsKey(CONFIG_API_KEY) ? config.get(CONFIG_API_KEY).toString() : null;
|
apiKey = config.containsKey(CONFIG_API_KEY) ? config.get(CONFIG_API_KEY).toString() : null;
|
||||||
}
|
}
|
||||||
@ -108,37 +113,41 @@ public class VoiceRSSTTSService implements TTSService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<Voice> getAvailableVoices() {
|
public Set<Voice> getAvailableVoices() {
|
||||||
return Collections.unmodifiableSet(voices);
|
Set<Voice> localVoices = voices;
|
||||||
|
return localVoices == null ? Set.of() : Collections.unmodifiableSet(localVoices);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<AudioFormat> getSupportedFormats() {
|
public Set<AudioFormat> getSupportedFormats() {
|
||||||
return Collections.unmodifiableSet(audioFormats);
|
Set<AudioFormat> localFormats = audioFormats;
|
||||||
|
return localFormats == null ? Set.of() : Collections.unmodifiableSet(localFormats);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AudioStream synthesize(String text, Voice voice, AudioFormat requestedFormat) throws TTSException {
|
public AudioStream synthesize(String text, Voice voice, AudioFormat requestedFormat) throws TTSException {
|
||||||
logger.debug("Synthesize '{}' for voice '{}' in format {}", text, voice.getUID(), requestedFormat);
|
logger.debug("Synthesize '{}' for voice '{}' in format {}", text, voice.getUID(), requestedFormat);
|
||||||
// Validate known api key
|
CachedVoiceRSSCloudImpl voiceRssCloud = voiceRssImpl;
|
||||||
if (apiKey == null) {
|
if (voiceRssCloud == null) {
|
||||||
throw new TTSException("Missing API key, configure it first before using");
|
throw new TTSException("The service is not correctly initialized");
|
||||||
}
|
}
|
||||||
// Validate arguments
|
// Validate known api key
|
||||||
if (text == null) {
|
String key = apiKey;
|
||||||
throw new TTSException("The passed text is null");
|
if (key == null) {
|
||||||
|
throw new TTSException("Missing API key, configure it first before using");
|
||||||
}
|
}
|
||||||
// trim text
|
// trim text
|
||||||
String trimmedText = text.trim();
|
String trimmedText = text.trim();
|
||||||
if (trimmedText.isEmpty()) {
|
if (trimmedText.isEmpty()) {
|
||||||
throw new TTSException("The passed text is empty");
|
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");
|
throw new TTSException("The passed voice is unsupported");
|
||||||
}
|
}
|
||||||
|
|
||||||
// now create the input stream for given text, locale, voice, codec and format.
|
// now create the input stream for given text, locale, voice, codec and format.
|
||||||
try {
|
try {
|
||||||
File cacheAudioFile = voiceRssImpl.getTextToSpeechAsFile(apiKey, trimmedText,
|
File cacheAudioFile = voiceRssCloud.getTextToSpeechAsFile(key, trimmedText,
|
||||||
voice.getLocale().toLanguageTag(), voice.getLabel(), getApiAudioCodec(requestedFormat),
|
voice.getLocale().toLanguageTag(), voice.getLabel(), getApiAudioCodec(requestedFormat),
|
||||||
getApiAudioFormat(requestedFormat));
|
getApiAudioFormat(requestedFormat));
|
||||||
return new VoiceRSSAudioStream(cacheAudioFile, requestedFormat);
|
return new VoiceRSSAudioStream(cacheAudioFile, requestedFormat);
|
||||||
@ -153,11 +162,16 @@ public class VoiceRSSTTSService implements TTSService {
|
|||||||
* Initializes voices.
|
* Initializes voices.
|
||||||
*
|
*
|
||||||
* @return The voices of this instance
|
* @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<>();
|
Set<Voice> voices = new HashSet<>();
|
||||||
for (Locale locale : voiceRssImpl.getAvailableLocales()) {
|
for (Locale locale : voiceRssCloud.getAvailableLocales()) {
|
||||||
for (String voiceLabel : voiceRssImpl.getAvailableVoices(locale)) {
|
for (String voiceLabel : voiceRssCloud.getAvailableVoices(locale)) {
|
||||||
voices.add(new VoiceRSSVoice(locale, voiceLabel));
|
voices.add(new VoiceRSSVoice(locale, voiceLabel));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -168,10 +182,15 @@ public class VoiceRSSTTSService implements TTSService {
|
|||||||
* Initializes audioFormats
|
* Initializes audioFormats
|
||||||
*
|
*
|
||||||
* @return The audio formats of this instance
|
* @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<>();
|
Set<AudioFormat> audioFormats = new HashSet<>();
|
||||||
for (String codec : voiceRssImpl.getAvailableAudioCodecs()) {
|
for (String codec : voiceRssCloud.getAvailableAudioCodecs()) {
|
||||||
switch (codec) {
|
switch (codec) {
|
||||||
case "MP3":
|
case "MP3":
|
||||||
audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_MP3, null, 16, 64000,
|
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
|
* @throws TTSException if {@code format} is not supported
|
||||||
*/
|
*/
|
||||||
private String getApiAudioFormat(AudioFormat format) throws TTSException {
|
private String getApiAudioFormat(AudioFormat format) throws TTSException {
|
||||||
final int bitDepth = format.getBitDepth() != null ? format.getBitDepth() : 16;
|
final Integer formatBitDepth = format.getBitDepth();
|
||||||
final Long frequency = format.getFrequency() != null ? format.getFrequency() : 44_100L;
|
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);
|
final String apiFrequency = FREQUENCY_MAP.get(frequency);
|
||||||
|
|
||||||
if (apiFrequency == null || (bitDepth != 8 && bitDepth != 16)) {
|
if (apiFrequency == null || (bitDepth != 8 && bitDepth != 16)) {
|
||||||
throw new TTSException("Unsupported audio format: " + format);
|
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:
|
case AudioFormat.CODEC_PCM_ALAW:
|
||||||
return "alaw_" + apiFrequency + "_mono";
|
return "alaw_" + apiFrequency + "_mono";
|
||||||
case AudioFormat.CODEC_PCM_ULAW:
|
case AudioFormat.CODEC_PCM_ULAW:
|
||||||
@ -310,7 +332,7 @@ public class VoiceRSSTTSService implements TTSService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getLabel(Locale locale) {
|
public String getLabel(@Nullable Locale locale) {
|
||||||
return "VoiceRSS";
|
return "VoiceRSS";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import java.io.BufferedReader;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.voice.voicerss.internal.cloudapi.CachedVoiceRSSCloudImpl;
|
import org.openhab.voice.voicerss.internal.cloudapi.CachedVoiceRSSCloudImpl;
|
||||||
@ -73,7 +74,10 @@ public class CreateTTSCache {
|
|||||||
File inputFile = new File(inputFileName);
|
File inputFile = new File(inputFileName);
|
||||||
if (!inputFile.exists()) {
|
if (!inputFile.exists()) {
|
||||||
usage();
|
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;
|
return RC_INPUT_FILE_NOT_FOUND;
|
||||||
}
|
}
|
||||||
generateCacheForFile(apiKey, cacheDir, locale, voice, codec, format, inputFileName);
|
generateCacheForFile(apiKey, cacheDir, locale, voice, codec, format, inputFileName);
|
||||||
@ -85,22 +89,26 @@ public class CreateTTSCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void usage() {
|
private void usage() {
|
||||||
System.out.println("Usage: java org.openhab.voice.voicerss.tool.CreateTTSCache <args>");
|
PrintStream printStream = System.out;
|
||||||
System.out.println(
|
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> ]");
|
"Arguments: --api-key <key> <cache-dir> <locale> <voice> { <text> | @inputfile } [ <codec> <format> ]");
|
||||||
System.out.println(" key the VoiceRSS API Key, e.g. \"123456789\"");
|
printStream.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\"");
|
printStream.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\"");
|
printStream.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");
|
printStream.println(" voice the voice, \"default\" for the default voice");
|
||||||
System.out.println(" text the text to create audio file for, e.g. \"Hello World\"");
|
printStream.println(" text the text to create audio file for, e.g. \"Hello World\"");
|
||||||
System.out.println(
|
printStream.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");
|
printStream.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");
|
printStream.println(" format the audio format, \"44khz_16bit_mono\" by default");
|
||||||
System.out.println();
|
printStream.println();
|
||||||
System.out.println(
|
printStream.println(
|
||||||
"Sample: java org.openhab.voice.voicerss.tool.CreateTTSCache --api-key 1234567890 cache en-US default @messages.txt");
|
"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 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,
|
private void generateCacheForMessage(String apiKey, String cacheDir, String locale, String voice, String codec,
|
||||||
String format, String msg) throws IOException {
|
String format, String msg) throws IOException {
|
||||||
|
PrintStream printStream;
|
||||||
String trimmedMsg = msg.trim();
|
String trimmedMsg = msg.trim();
|
||||||
if (trimmedMsg.length() == 0) {
|
if (trimmedMsg.length() == 0) {
|
||||||
System.err.println("Ignore msg=''");
|
printStream = System.err;
|
||||||
|
if (printStream != null) {
|
||||||
|
printStream.println("Ignore msg=''");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
CachedVoiceRSSCloudImpl impl = new CachedVoiceRSSCloudImpl(cacheDir, false);
|
CachedVoiceRSSCloudImpl impl = new CachedVoiceRSSCloudImpl(cacheDir, false);
|
||||||
File cachedFile = impl.getTextToSpeechAsFile(apiKey, trimmedMsg, locale, voice, codec, format);
|
File cachedFile = impl.getTextToSpeechAsFile(apiKey, trimmedMsg, locale, voice, codec, format);
|
||||||
System.out.println("Created cached audio for locale='" + locale + "', voice='" + voice + "', msg='"
|
printStream = System.out;
|
||||||
+ trimmedMsg + "' to file=" + cachedFile);
|
if (printStream != null) {
|
||||||
|
printStream.println("Created cached audio for locale='" + locale + "', voice='" + voice + "', msg='"
|
||||||
|
+ trimmedMsg + "' to file=" + cachedFile);
|
||||||
|
}
|
||||||
} catch (IllegalStateException | IOException ex) {
|
} catch (IllegalStateException | IOException ex) {
|
||||||
System.err.println("Failed to create cached audio for locale='" + locale + "', voice='" + voice + "',msg='"
|
printStream = System.err;
|
||||||
+ trimmedMsg + "'");
|
if (printStream != null) {
|
||||||
|
printStream.println("Failed to create cached audio for locale='" + locale + "', voice='" + voice
|
||||||
|
+ "',msg='" + trimmedMsg + "'");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user