+++ /dev/null
-/**
- * Copyright (c) 2010-2023 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.mimic.internal;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.core.audio.AudioException;
-import org.openhab.core.audio.AudioFormat;
-import org.openhab.core.audio.FileAudioStream;
-
-/**
- * A FileAudioStream that autodelete after it and its clone are closed
- * Useful to not congest temporary directory
- *
- * @author Gwendal Roulleau - Initial contribution
- */
-@NonNullByDefault
-public class AutoDeleteFileAudioStream extends FileAudioStream {
-
- private final File file;
- private final AudioFormat audioFormat;
- private final List<ClonedFileInputStream> clonedAudioStreams = new ArrayList<>(1);
- private boolean isOpen = true;
-
- public AutoDeleteFileAudioStream(File file, AudioFormat format) throws AudioException {
- super(file, format);
- this.file = file;
- this.audioFormat = format;
- }
-
- @Override
- public void close() throws IOException {
- super.close();
- this.isOpen = false;
- deleteIfPossible();
- }
-
- protected void deleteIfPossible() {
- boolean aClonedStreamIsOpen = clonedAudioStreams.stream().anyMatch(as -> as.isOpen);
- if (!isOpen && !aClonedStreamIsOpen) {
- file.delete();
- }
- }
-
- @Override
- public InputStream getClonedStream() throws AudioException {
- ClonedFileInputStream clonedInputStream = new ClonedFileInputStream(this, file, audioFormat);
- clonedAudioStreams.add(clonedInputStream);
- return clonedInputStream;
- }
-
- private static class ClonedFileInputStream extends FileAudioStream {
- protected boolean isOpen = true;
- private final AutoDeleteFileAudioStream parent;
-
- public ClonedFileInputStream(AutoDeleteFileAudioStream parent, File file, AudioFormat audioFormat)
- throws AudioException {
- super(file, audioFormat);
- this.parent = parent;
- }
-
- @Override
- public void close() throws IOException {
- super.close();
- this.isOpen = false;
- parent.deleteIfPossible();
- }
- }
-}
*/
package org.openhab.voice.mimic.internal;
-import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.StandardCopyOption;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
-import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
-import org.openhab.core.OpenHAB;
-import org.openhab.core.audio.AudioException;
import org.openhab.core.audio.AudioFormat;
import org.openhab.core.audio.AudioStream;
import org.openhab.core.config.core.ConfigurableService;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.io.net.http.HttpRequestBuilder;
+import org.openhab.core.voice.AbstractCachedTTSService;
+import org.openhab.core.voice.TTSCache;
import org.openhab.core.voice.TTSException;
import org.openhab.core.voice.TTSService;
import org.openhab.core.voice.Voice;
* @author Gwendal Roulleau - Initial contribution
*/
@Component(configurationPid = MimicTTSService.SERVICE_PID, property = Constants.SERVICE_PID + "="
- + MimicTTSService.SERVICE_PID)
+ + MimicTTSService.SERVICE_PID, service = TTSService.class)
@ConfigurableService(category = MimicTTSService.SERVICE_CATEGORY, label = MimicTTSService.SERVICE_NAME
+ " Text-to-Speech", description_uri = MimicTTSService.SERVICE_CATEGORY + ":" + MimicTTSService.SERVICE_ID)
@NonNullByDefault
-public class MimicTTSService implements TTSService {
+public class MimicTTSService extends AbstractCachedTTSService {
private final Logger logger = LoggerFactory.getLogger(MimicTTSService.class);
* Configuration parameters
*/
private static final String PARAM_URL = "url";
- private static final String PARAM_WORKAROUNDSERVLETSINK = "workaroundServletSink";
private static final String PARAM_SPEAKINGRATE = "speakingRate";
private static final String PARAM_AUDIOVOLATITLITY = "audioVolatility";
private static final String PARAM_PHONEMEVOLATITLITY = "phonemeVolatility";
private final HttpClient httpClient;
@Activate
- public MimicTTSService(final @Reference HttpClientFactory httpClientFactory, Map<String, Object> config) {
+ public MimicTTSService(final @Reference HttpClientFactory httpClientFactory, @Reference TTSCache ttsCache,
+ Map<String, Object> config) {
+ super(ttsCache);
updateConfig(config);
this.httpClient = httpClientFactory.getCommonHttpClient();
}
config.url = param.toString();
}
- // workaround
- param = newConfig.get(PARAM_WORKAROUNDSERVLETSINK);
- if (param != null) {
- config.workaroundServletSink = Boolean.parseBoolean(param.toString());
- }
-
// audio volatility
try {
param = newConfig.get(PARAM_AUDIOVOLATITLITY);
* @throws TTSException in case the service is unavailable or a parameter is invalid.
*/
@Override
- public AudioStream synthesize(String text, Voice voice, AudioFormat requestedFormat) throws TTSException {
-
+ public AudioStream synthesizeForCache(String text, Voice voice, AudioFormat requestedFormat) throws TTSException {
if (!availableVoices.contains(voice)) {
// let a chance for the service to update :
refreshVoices();
}
InputStream inputStreamFromMimic = inputStreamResponseListener.getInputStream();
- try {
- if (!config.workaroundServletSink) {
- return new InputStreamAudioStream(inputStreamFromMimic, AUDIO_FORMAT, length);
- } else {
- // Some audio sinks use the openHAB servlet to get audio. This servlet require the
- // getClonedStream()
- // method
- // So we cache the file on disk, thus implementing the method thanks to FileAudioStream.
- return createTemporaryFile(inputStreamFromMimic, AUDIO_FORMAT);
- }
- } catch (TTSException e) {
- try {
- inputStreamFromMimic.close();
- } catch (IOException e1) {
- }
- throw e;
- }
-
+ return new InputStreamAudioStream(inputStreamFromMimic, AUDIO_FORMAT, length);
} else {
String errorMessage = "Cannot get wav from mimic url " + urlTTS + " with HTTP response code "
+ response.getStatus() + " for reason " + response.getReason();
}
}
- private AudioStream createTemporaryFile(InputStream inputStream, AudioFormat audioFormat) throws TTSException {
- File mimicDirectory = new File(OpenHAB.getUserDataFolder(), "mimic");
- mimicDirectory.mkdir();
+ @Override
+ public String getCacheKey(String text, Voice voice, AudioFormat requestedFormat) {
+ MessageDigest md;
try {
- File tempFile = File.createTempFile(UUID.randomUUID().toString(), ".wav", mimicDirectory);
- tempFile.deleteOnExit();
- Files.copy(inputStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
- return new AutoDeleteFileAudioStream(tempFile, audioFormat);
- } catch (AudioException | IOException e) {
- throw new TTSException("Cannot create temporary audio file", e);
+ md = MessageDigest.getInstance("MD5");
+ } catch (NoSuchAlgorithmException e) {
+ return "nomd5algorithm";
}
+ byte[] binaryKey = ((text + voice.getUID() + requestedFormat.toString() + config.speakingRate
+ + config.audioVolatility + config.phonemeVolatility).getBytes());
+ return String.format("%032x", new BigInteger(1, md.digest(binaryKey)));
}
}