added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.voice.pollytts</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,50 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons
== Third-party Content
aws-java-sdk-core
* License: Apache 2.0 License
* Project: https://aws.amazon.com/sdk-for-java/
* Source: https://github.com/aws/aws-sdk-java/tree/master/aws-java-sdk-core
aws-java-sdk-polly
* License: Apache 2.0 License
* Project: https://aws.amazon.com/sdk-for-java/
* Source: https://github.com/aws/aws-sdk-java/tree/master/aws-java-sdk-polly
commons-logging
* License: Apache 2.0 License
* Project: https://commons.apache.org/proper/commons-logging/
* Source: https://gitbox.apache.org/repos/asf/?p=commons-logging.git
httpclient
* License: Apache 2.0 License
* Project: https://hc.apache.org/httpcomponents-client-ga
* Source: https://hc.apache.org/httpcomponents-client-ga
httpcore
* License: Apache 2.0 License
* Project: http://hc.apache.org/httpcomponents-core-ga
* Source: http://hc.apache.org/httpcomponents-core-ga
jackson
* License: Apache 2.0 license
* Project: https://github.com/FasterXML/jackson
* Source: https://github.com/FasterXML/jackson
joda-time
* License: Apache 2.0 License
* Project: https://www.joda.org/joda-time/
* Source: https://github.com/JodaOrg/joda-time

View File

@@ -0,0 +1,69 @@
# Polly Text-to-Speech
PollyTTS is a voice service utilizing the Internet based text-to-speech (TTS) service [Amazon Polly](https://aws.amazon.com/polly/).
The service generates speech from both plain text input and text with Speech Synthesis Markup Language (SSML) [tags](https://docs.aws.amazon.com/polly/latest/dg/supported-ssml.html).
There are servers set in various geographic [regions](https://docs.aws.amazon.com/general/latest/gr/rande.html#pol_region).
API keys provided by Amazon are required to get access to the service.
Amazon Polly has a wide selection of [voices and languages](https://aws.amazon.com/polly/features/#Wide_Selection_of_Voices_and_Languages).
Be aware, that using this service may incur costs on your AWS account.
You can find pricing information on the [documentation page](https://aws.amazon.com/polly/pricing/).
## Obtaining Credentials
* Sign up for Amazon Web Services (AWS). [link](https://portal.aws.amazon.com/billing/signup)
When you sign up for AWS, your account is automatically signed up for all services in AWS, including Amazon Polly.
* Create an IAM User. [link](https://docs.aws.amazon.com/polly/latest/dg/setting-up.html)
Services in AWS, such as Amazon Polly, require that you provide credentials when you access them so that the service can determine whether you have permissions to access the resources owned by that service.
Within the AWS console, you can create access keys for your AWS account to access the Polly API.
To use the service you will need the **access key**, **secret key** and **server region**.
## Service Configuration
Using your favorite configuration UI (e.g. Paper UI) edit **Services/Voice/Polly Text-to-Speech** settings and set:
* **Access Key** - The AWS credentials access key (required).
* **Secret Key** - The AWS credentials secret key (required).
* **Service Region** - The service region used for accessing Polly (required). To reduce latency select the region closest to you. E.g. "eu-west-1" (see [regions](https://docs.aws.amazon.com/general/latest/gr/rande.html#pol_region))
The PollyTTS service caches audio files from previous requests.
This reduces traffic, improves performance, reduces the number of requests and provides offline functionality.
* **Cache Expiration** - Cache expiration in days.
When cache files are used their time stamps are updated, unused files are purged if their time stamp exceeds the specified age.
The default value of 0 disables this functionality.
A value of 365 removes files that have been unused for a year.
* **Audio Format** - Allows for overriding the system default audio format.
Use "default" to select the system default audio format.
The default audio format can be overriden with the value "mp3" or "ogg".
### Service Configuration via Text files
Create a new file in `$OPENHAB_ROOT/conf/services` named `pollytts.cfg`
It's contents should look similar to:
```
org.openhab.pollytts:accessKey=ACCESS_KEY_ID
org.openhab.pollytts:secretKey=SECRET_KEY
org.openhab.pollytts:serviceRegion=SERVICE_REGION
org.openhab.pollytts:cacheExpiration=EXPIRATION_IN_DAYS
```
These have the same meanings as described in the **Service Configuration** block above.
## Rule Examples
```
say("Hello there")
say("Hello there", "pollytts:Joanne", "enhancedjavasound")
say("" + item.state, "pollytts:Joey", "enhancedjavasound")
say("<speak>Children, come to dinner <prosody volume='x-loud'>Right now!</prosody></speak>")
```

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.voice.pollytts</artifactId>
<name>openHAB Add-ons :: Bundles :: Voice :: Polly Text-to-Speech</name>
<dependencies>
<dependency>
<groupId>org.openhab.osgiify</groupId>
<artifactId>com.amazonaws.aws-java-sdk-core</artifactId>
<version>1.11.490</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.openhab.osgiify</groupId>
<artifactId>com.amazonaws.aws-java-sdk-polly</artifactId>
<version>1.11.490</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.voice.pollytts-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-voice-pollytts" description="Polly Text-to-Speech" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature dependency="true">openhab.tp-jackson</feature>
<bundle dependency="true">mvn:com.fasterxml.jackson.dataformat/jackson-dataformat-cbor/2.9.9</bundle>
<bundle dependency="true">mvn:org.apache.httpcomponents/httpcore-osgi/4.4.9</bundle>
<bundle dependency="true">mvn:org.apache.httpcomponents/httpclient-osgi/4.5.5</bundle>
<bundle dependency="true">mvn:org.openhab.osgiify/com.amazonaws.aws-java-sdk-core/1.11.490</bundle>
<bundle dependency="true">mvn:org.openhab.osgiify/com.amazonaws.aws-java-sdk-polly/1.11.490</bundle>
<bundle dependency="true">mvn:commons-logging/commons-logging/1.2</bundle>
<bundle dependency="true">mvn:joda-time/joda-time/2.8.1</bundle>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.voice.pollytts/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.voice.pollytts.internal;
import java.io.File;
import org.openhab.core.audio.AudioException;
import org.openhab.core.audio.AudioFormat;
import org.openhab.core.audio.AudioStream;
import org.openhab.core.audio.FileAudioStream;
/**
* Implementation of the {@link AudioStream} interface for the {@link PollyTTSService}.
* It simply uses a {@link FileAudioStream} which is doing all the necessary work,
* e.g. supporting MP3 and WAV files with fixed stream length.
*
* @author Robert Hillman - Initial contribution
*/
class PollyTTSAudioStream extends FileAudioStream {
/**
* main method the passes the audio file to system audio services
*/
public PollyTTSAudioStream(File audioFile, AudioFormat format) throws AudioException {
super(audioFile, format);
}
}

View File

@@ -0,0 +1,239 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.voice.pollytts.internal;
import static java.util.stream.Collectors.toSet;
import static org.openhab.core.audio.AudioFormat.*;
import static org.openhab.voice.pollytts.internal.PollyTTSService.*;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.openhab.core.audio.AudioException;
import org.openhab.core.audio.AudioFormat;
import org.openhab.core.audio.AudioStream;
import org.openhab.core.config.core.ConfigConstants;
import org.openhab.core.config.core.ConfigurableService;
import org.openhab.core.voice.TTSException;
import org.openhab.core.voice.TTSService;
import org.openhab.core.voice.Voice;
import org.openhab.voice.pollytts.internal.cloudapi.CachedPollyTTSCloudImpl;
import org.openhab.voice.pollytts.internal.cloudapi.PollyTTSConfig;
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;
import org.slf4j.LoggerFactory;
/**
* This is a TTS service implementation for using Polly Text-to-Speech.
*
* @author Robert Hillman - Initial contribution
*/
@Component(configurationPid = SERVICE_PID, property = { Constants.SERVICE_PID + "=" + SERVICE_PID,
ConfigurableService.SERVICE_PROPERTY_LABEL + "=" + SERVICE_NAME + " Text-to-Speech",
ConfigurableService.SERVICE_PROPERTY_DESCRIPTION_URI + "=" + SERVICE_CATEGORY + ":" + SERVICE_ID,
ConfigurableService.SERVICE_PROPERTY_CATEGORY + "=" + SERVICE_CATEGORY })
public class PollyTTSService implements TTSService {
/**
* Service name
*/
static final String SERVICE_NAME = "Polly";
/**
* Service id
*/
static final String SERVICE_ID = "pollytts";
/**
* Service category
*/
static final String SERVICE_CATEGORY = "voice";
/**
* Service pid
*/
static final String SERVICE_PID = "org.openhab." + SERVICE_CATEGORY + "." + SERVICE_ID;
/**
* Cache folder under $userdata
*/
private static final String CACHE_FOLDER_NAME = "cache";
private final Logger logger = LoggerFactory.getLogger(PollyTTSService.class);
/**
* We need the cached implementation to allow for FixedLengthAudioStream.
*/
private CachedPollyTTSCloudImpl pollyTTSImpl;
/**
* Set of supported voices
*/
private final Set<Voice> voices = new HashSet<>();
/**
* Set of supported audio formats
*/
private final Set<AudioFormat> audioFormats = new HashSet<>();
private PollyTTSConfig pollyTTSConfig;
@Activate
protected void activate(Map<String, Object> config) {
modified(config);
}
@Modified
protected void modified(Map<String, Object> config) {
try {
pollyTTSConfig = new PollyTTSConfig(config);
logger.debug("Using configuration {}", config);
// create cache folder
File cacheFolder = new File(new File(ConfigConstants.getUserDataFolder(), CACHE_FOLDER_NAME), SERVICE_PID);
if (!cacheFolder.exists()) {
cacheFolder.mkdirs();
}
logger.info("Using cache folder {}", cacheFolder.getAbsolutePath());
pollyTTSImpl = new CachedPollyTTSCloudImpl(pollyTTSConfig, cacheFolder);
audioFormats.clear();
audioFormats.addAll(initAudioFormats());
voices.clear();
voices.addAll(initVoices());
logger.debug("PollyTTS service initialized");
} catch (IllegalArgumentException e) {
logger.warn("Failed to initialize PollyTTS: {}", e.getMessage());
} catch (Exception e) {
logger.warn("Failed to initialize PollyTTS", e);
}
}
@Override
public Set<Voice> getAvailableVoices() {
return Collections.unmodifiableSet(voices);
}
@Override
public Set<AudioFormat> getSupportedFormats() {
return Collections.unmodifiableSet(audioFormats);
}
/**
* obtain audio stream from cache or Amazon Polly service and return it to play the audio
*/
@Override
public AudioStream synthesize(String inText, Voice voice, AudioFormat requestedFormat) throws TTSException {
logger.debug("Synthesize '{}' in format {}", inText, requestedFormat);
logger.debug("voice UID: '{}' voice label: '{}' voice Locale: {}", voice.getUID(), voice.getLabel(),
voice.getLocale());
// Validate arguments
// trim text
String text = inText.trim();
if (text == null || text.isEmpty()) {
throw new TTSException("The passed text is null or empty");
}
if (!voices.contains(voice)) {
throw new TTSException("The passed voice is unsupported");
}
boolean isAudioFormatSupported = audioFormats.stream()
.filter(audioFormat -> audioFormat.isCompatible(requestedFormat)).findAny().isPresent();
if (!isAudioFormatSupported) {
throw new TTSException("The passed AudioFormat is unsupported");
}
// now create the input stream for given text, locale, format. There is
// only a default voice
try {
File cacheAudioFile = pollyTTSImpl.getTextToSpeechAsFile(text, voice.getLabel(),
getApiAudioFormat(requestedFormat));
if (cacheAudioFile == null) {
throw new TTSException("Could not read from PollyTTS service");
}
logger.debug("Audio Stream for '{}' in format {}", text, requestedFormat);
AudioStream audioStream = new PollyTTSAudioStream(cacheAudioFile, requestedFormat);
return audioStream;
} catch (AudioException ex) {
throw new TTSException("Could not create AudioStream: " + ex.getMessage(), ex);
} catch (IOException ex) {
throw new TTSException("Could not read from PollyTTS service: " + ex.getMessage(), ex);
}
}
private Set<Voice> initVoices() {
// @formatter:off
return pollyTTSImpl.getAvailableLocales().stream()
.flatMap(locale ->
pollyTTSImpl.getAvailableVoices(locale).stream()
.map(label -> new PollyTTSVoice(locale, label)))
.collect(toSet());
// @formatter:on
}
private Set<AudioFormat> initAudioFormats() {
// @formatter:off
return pollyTTSImpl.getAvailableAudioFormats().stream()
.map(this::getAudioFormat)
.collect(toSet());
// @formatter:on
}
private AudioFormat getAudioFormat(String apiFormat) {
if (CODEC_MP3.equals(apiFormat)) {
// use by default: MP3, 22khz_16bit_mono with bitrate 64 kbps
return new AudioFormat(CONTAINER_NONE, CODEC_MP3, null, 16, 64000, 22050L);
} else if (CONTAINER_OGG.equals(apiFormat)) {
// use by default: OGG, 22khz_16bit_mono
return new AudioFormat(CONTAINER_OGG, CODEC_VORBIS, null, 16, null, 22050L);
} else {
throw new IllegalArgumentException("Audio format " + apiFormat + " not yet supported");
}
}
private String getApiAudioFormat(AudioFormat format) {
if (!"default".equals(pollyTTSConfig.getAudioFormat())) {
// Override system specified with user preferred value
return pollyTTSConfig.getAudioFormat();
}
if (CODEC_MP3.equals(format.getCodec())) {
return CODEC_MP3;
} else if (CODEC_VORBIS.equals(format.getCodec())) {
return CONTAINER_OGG;
} else {
throw new IllegalArgumentException("Audio format " + format.getCodec() + " not yet supported");
}
}
@Override
public String getId() {
return "pollytts";
}
@Override
public String getLabel(Locale locale) {
return "PollyTTS";
}
}

View File

@@ -0,0 +1,76 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.voice.pollytts.internal;
import java.util.Locale;
import org.openhab.core.voice.Voice;
/**
* Implementation of the Voice interface for PollyTTS.
*
* @author Robert Hillman - Initial contribution
*/
public class PollyTTSVoice implements Voice {
/**
* Voice locale
*/
private final Locale locale;
/**
* Voice label
*/
private final String label;
/**
* Constructs a PollyTTS Voice for the passed data
*
* @param locale
* The Locale of the voice
* @param label
* The label of the voice
*/
public PollyTTSVoice(Locale locale, String label) {
this.locale = locale;
this.label = label;
}
/**
* Globally unique identifier of the voice.
*
* @return A String uniquely identifying the voice globally
*/
@Override
public String getUID() {
return "pollytts:" + label;
}
/**
* The voice label, used for GUI's or VUI's
*
* @return The voice label, may not be globally unique
*/
@Override
public String getLabel() {
return label;
}
/**
* @inheritDoc
*/
@Override
public Locale getLocale() {
return locale;
}
}

View File

@@ -0,0 +1,162 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.voice.pollytts.internal.cloudapi;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class implements a cache for the retrieved audio data. It will preserve them in the file system,
* as audio files with an additional .txt file to indicate what content is in the audio file.
*
* @author Robert Hillman - Initial contribution
*/
public class CachedPollyTTSCloudImpl extends PollyTTSCloudImpl {
private static final int READ_BUFFER_SIZE = 4096;
private final Logger logger = LoggerFactory.getLogger(CachedPollyTTSCloudImpl.class);
private final File cacheFolder;
/**
* Create the file folder to hold the the cached speech files.
* check to make sure the directory exist and
* create it if necessary
*/
public CachedPollyTTSCloudImpl(PollyTTSConfig config, File cacheFolder) throws IOException {
super(config);
this.cacheFolder = cacheFolder;
}
/**
* Fetch the specified text as an audio file.
* The audio file will be obtained from the cached folder if it
* exist or generated by use to the external voice service.
* The cached file txt description time stamp will be updated
* to identify last use.
*/
public File getTextToSpeechAsFile(String text, String label, String audioFormat) throws IOException {
String fileNameInCache = getUniqueFilenameForText(text, label);
// check if in cache
File audioFileInCache = new File(cacheFolder, fileNameInCache + "." + audioFormat.toLowerCase());
if (audioFileInCache.exists()) {
// update use date
updateTimeStamp(audioFileInCache);
updateTimeStamp(new File(cacheFolder, fileNameInCache + ".txt"));
purgeAgedFiles();
return audioFileInCache;
}
// if not in cache, get audio data and put to cache
try (InputStream is = getTextToSpeech(text, label, audioFormat);
FileOutputStream fos = new FileOutputStream(audioFileInCache)) {
copyStream(is, fos);
// write text to file for transparency too
// this allows to know which contents is in which audio file
File txtFileInCache = new File(cacheFolder, fileNameInCache + ".txt");
writeText(txtFileInCache, text);
// return from cache
return audioFileInCache;
} catch (IOException ex) {
logger.warn("Could not write {} to cache, return null", audioFileInCache, ex);
return null;
}
}
/**
* Gets a unique filename for a give text, by creating a MD5 hash of it. It
* will be preceded by the voice label.
*
* Sample: "Robert_00a2653ac5f77063bc4ea2fee87318d3"
*/
private String getUniqueFilenameForText(String text, String label) {
MessageDigest md;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException ex) {
logger.error("Could not create MD5 hash for '{}'", text, ex);
return null;
}
byte[] md5Hash = md.digest(text.getBytes(StandardCharsets.UTF_8));
BigInteger bigInt = new BigInteger(1, md5Hash);
String hashtext = bigInt.toString(16);
// Now we need to zero pad it if you actually want the full 32
// chars.
while (hashtext.length() < 32) {
hashtext = "0" + hashtext;
}
String fileName = label + "_" + hashtext;
return fileName;
}
// helper methods
private void copyStream(InputStream inputStream, OutputStream outputStream) throws IOException {
byte[] bytes = new byte[READ_BUFFER_SIZE];
int read = inputStream.read(bytes, 0, READ_BUFFER_SIZE);
while (read > 0) {
outputStream.write(bytes, 0, read);
read = inputStream.read(bytes, 0, READ_BUFFER_SIZE);
}
}
private void writeText(File file, String text) throws IOException {
try (OutputStream outputStream = new FileOutputStream(file)) {
outputStream.write(text.getBytes(StandardCharsets.UTF_8));
}
}
private void updateTimeStamp(File file) throws IOException {
// update use date for cache management
file.setLastModified(System.currentTimeMillis());
}
private void purgeAgedFiles() throws IOException {
// just exit if expiration set to 0/disabled
if (config.getExpireDate() == 0) {
return;
}
long now = new Date().getTime();
long diff = now - config.getLastDelete();
// only execute ~ once every 2 days if cache called
long oneDayMillis = TimeUnit.DAYS.toMillis(1);
logger.debug("PollyTTS cache cleaner lastdelete {}", diff);
if (diff > (2 * oneDayMillis)) {
config.setLastDelete(now);
long xDaysAgo = config.getExpireDate() * oneDayMillis;
// Now search folders and delete old files
int filesDeleted = 0;
for (File file : cacheFolder.listFiles()) {
diff = now - file.lastModified();
if (diff > xDaysAgo) {
filesDeleted++;
file.delete();
}
}
logger.debug("PollyTTS cache cleaner deleted '{}' aged files", filesDeleted);
}
}
}

View File

@@ -0,0 +1,133 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.voice.pollytts.internal.cloudapi;
import static java.util.stream.Collectors.*;
import static org.openhab.core.audio.AudioFormat.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.polly.AmazonPolly;
import com.amazonaws.services.polly.AmazonPollyClientBuilder;
import com.amazonaws.services.polly.model.DescribeVoicesRequest;
import com.amazonaws.services.polly.model.OutputFormat;
import com.amazonaws.services.polly.model.SynthesizeSpeechRequest;
import com.amazonaws.services.polly.model.TextType;
import com.amazonaws.services.polly.model.Voice;
/**
* This class implements the Cloud service for PollyTTS.
*
* The implementation supports:
* <ul>
* <li>All languages</li>
* <li>All voices</li>
* <li>MP3 and OGG formats</li>
* </ul>
*
* @author Robert Hillman - Initial contribution
*/
public class PollyTTSCloudImpl {
private static final Set<String> SUPPORTED_AUDIO_FORMATS = Collections
.unmodifiableSet(Stream.of(CODEC_MP3, CONTAINER_OGG).collect(toSet()));
protected final PollyTTSConfig config;
private final AmazonPolly client;
private final Map<String, String> labelToID;
private final List<Voice> voices;
public PollyTTSCloudImpl(PollyTTSConfig config) {
this.config = config;
AWSCredentials credentials = new BasicAWSCredentials(config.getAccessKey(), config.getSecretKey());
client = AmazonPollyClientBuilder.standard().withRegion(config.getServiceRegion())
.withCredentials(new AWSStaticCredentialsProvider(credentials)).build();
voices = client.describeVoices(new DescribeVoicesRequest()).getVoices();
// create voice to ID translation for service invocation
labelToID = voices.stream().collect(toMap(Voice::getName, Voice::getId));
}
/**
* Get all supported audio formats by the TTS service. This includes MP3,
* WAV and more audio formats as used in APIs.
*/
public Set<String> getAvailableAudioFormats() {
return SUPPORTED_AUDIO_FORMATS;
}
public Set<Locale> getAvailableLocales() {
// @formatter:off
return voices.stream()
.map(voice -> Locale.forLanguageTag(voice.getLanguageCode()))
.collect(toSet());
// @formatter:on
}
public Set<String> getAvailableVoices() {
// @formatter:off
return voices.stream()
.map(Voice::getName)
.collect(toSet());
// @formatter:on
}
public Set<String> getAvailableVoices(Locale locale) {
// @formatter:off
return voices.stream()
.filter(voice -> voice.getLanguageCode().equalsIgnoreCase(locale.toLanguageTag()))
.map(Voice::getName)
.collect(toSet());
// @formatter:on
}
/**
* This method will return an input stream to an audio stream for the given
* parameters.
* Get the given text in specified locale and audio format as input stream.
*
* @param text
* the text to translate into speech
* @param label
* the voice Label to use
* @param audioFormat
* the audio format to use
* @return an InputStream to the audio data in specified format
* @throws IOException
* will be raised if the audio data can not be retrieved from
* cloud service
*/
public InputStream getTextToSpeech(String text, String label, String audioFormat) {
String voiceID = labelToID.get(label);
String format = audioFormat.toLowerCase();
if ("ogg".equals(format)) {
format = "ogg_vorbis";
}
TextType textType = text.startsWith("<speak>") ? TextType.Ssml : TextType.Text;
SynthesizeSpeechRequest request = new SynthesizeSpeechRequest().withTextType(textType).withText(text)
.withVoiceId(voiceID).withOutputFormat(OutputFormat.fromValue(format));
return client.synthesizeSpeech(request).getAudioStream();
}
}

View File

@@ -0,0 +1,110 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.voice.pollytts.internal.cloudapi;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* This class implements the PollyTTS configuration.
*
* @author Robert Hillman - Initial contribution
*/
public class PollyTTSConfig {
private static final String ACCESS_KEY = "accessKey";
private static final String SECRET_KEY = "secretKey";
private static final String SERVICE_REGION = "serviceRegion";
private static final String AUDIO_FORMAT = "audioFormat";
private static final String CACHE_EXPIRATION = "cacheExpiration";
private String accessKey = "";
private String secretKey = "";
private String serviceRegion = "eu-west-1";
private int expireDate = 0;
private String audioFormat = "default";
private long lastDelete;
public PollyTTSConfig(Map<String, Object> config) {
assertValidConfig(config);
accessKey = config.getOrDefault(ACCESS_KEY, accessKey).toString();
secretKey = config.getOrDefault(SECRET_KEY, secretKey).toString();
serviceRegion = config.getOrDefault(SERVICE_REGION, serviceRegion).toString();
audioFormat = config.getOrDefault(AUDIO_FORMAT, audioFormat).toString();
expireDate = (int) Double
.parseDouble(config.getOrDefault(CACHE_EXPIRATION, Double.toString(expireDate)).toString());
}
private void assertValidConfig(Map<String, Object> config) {
List<String> emptyParams = Stream.of(ACCESS_KEY, SECRET_KEY, SERVICE_REGION)
.filter(param -> config.get(param) == null || config.get(param).toString().isEmpty())
.collect(Collectors.toList());
if (!emptyParams.isEmpty()) {
throw new IllegalArgumentException(
"Missing configuration parameters: " + emptyParams.stream().collect(Collectors.joining(", ")));
}
}
public String getAccessKey() {
return accessKey;
}
public String getSecretKey() {
return secretKey;
}
public String getServiceRegion() {
return serviceRegion;
}
/**
* get the life time for cache files
*/
public int getExpireDate() {
return expireDate;
}
/**
* returns audio format specified for audio
*/
public String getAudioFormat() {
return audioFormat;
}
/**
* get the date when cache was cleaned last
*/
public long getLastDelete() {
return lastDelete;
}
/**
* set the date when cache was cleaned last
*/
public void setLastDelete(long lastDelete) {
this.lastDelete = lastDelete;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("PollyTTSConfig [accessKey=").append(accessKey).append(", secretKey=").append(secretKey)
.append(", serviceRegion=").append(serviceRegion).append(", expireDate=").append(expireDate)
.append(", audioFormat=").append(audioFormat).append(", lastDelete=").append(lastDelete).append("]");
return builder.toString();
}
}

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="voice:pollytts">
<parameter name="accessKey" type="text" required="true">
<label>Access Key</label>
<description>The access key part of the AWS credentials. You need to register to get a key.</description>
</parameter>
<parameter name="secretKey" type="text" required="true">
<label>Secret Key</label>
<description>The secret key part of the AWS credentials. You need to register to get a key.</description>
</parameter>
<parameter name="serviceRegion" type="text" required="true">
<label>Service Region</label>
<description>The service region used for accessing Polly. To reduce latency select the region closest to you.</description>
<options>
<option value="ap-south-1">Asia Pacific (Mumbai)</option>
<option value="ap-northeast-2">Asia Pacific (Seoul)</option>
<option value="ap-southeast-1">Asia Pacific (Singapore)</option>
<option value="ap-southeast-2">Asia Pacific (Sydney)</option>
<option value="ap-northeast-1">Asia Pacific (Tokyo)</option>
<option value="us-gov-west-1">AWS GovCloud (US)</option>
<option value="ca-central-1">Canada (Central)</option>
<option value="cn-northwest-1">China (Ningxia)</option>
<option value="eu-central-1">EU (Frankfurt)</option>
<option value="eu-west-1">EU (Ireland)</option>
<option value="eu-west-2">EU (London)</option>
<option value="eu-west-3">EU (Paris)</option>
<option value="sa-east-1">South America (São Paulo)</option>
<option value="us-east-1">US East (N. Virginia)</option>
<option value="us-east-2">US East (Ohio)</option>
<option value="us-west-1">US West (N. California)</option>
<option value="us-west-2">US West (Oregon)</option>
</options>
<default>eu-west-1</default>
</parameter>
<parameter name="audioFormat" type="text">
<label>Audio Format</label>
<description>Allows for overriding the system default audio format. "MP3" and "OGG" are the only audio formats that
are supported.</description>
<options>
<option value="default">Use system default</option>
<option value="MP3">MP3</option>
<option value="OGG">OGG</option>
</options>
<default>default</default>
</parameter>
<parameter name="cacheExpiration" type="text">
<label>Cache Expiration</label>
<description>Determines the age in days when unused cached files are purged.
Use 0 to disable this functionality.</description>
<default>0</default>
</parameter>
</config-description>
</config-description:config-descriptions>