2 * Copyright (c) 2010-2022 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.voice.voicerss.internal.cloudapi;
16 import java.io.FileNotFoundException;
17 import java.io.FileOutputStream;
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.io.OutputStream;
21 import java.math.BigInteger;
22 import java.nio.charset.StandardCharsets;
23 import java.security.MessageDigest;
24 import java.security.NoSuchAlgorithmException;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
30 * This class implements a cache for the retrieved audio data. It will preserve
31 * them in file system, as audio files with an additional .txt file to indicate
32 * what content is in the audio file.
34 * @author Jochen Hiller - Initial contribution
36 public class CachedVoiceRSSCloudImpl extends VoiceRSSCloudImpl {
38 private final Logger logger = LoggerFactory.getLogger(CachedVoiceRSSCloudImpl.class);
40 private final File cacheFolder;
45 private static final int READ_BUFFER_SIZE = 4096;
47 public CachedVoiceRSSCloudImpl(String cacheFolderName) {
48 if (cacheFolderName == null) {
49 throw new IllegalStateException("Folder for cache must be defined");
51 // Lazy create the cache folder
52 cacheFolder = new File(cacheFolderName);
53 if (!cacheFolder.exists()) {
58 public File getTextToSpeechAsFile(String apiKey, String text, String locale, String voice, String audioFormat)
60 String fileNameInCache = getUniqueFilenameForText(text, locale, voice);
62 File audioFileInCache = new File(cacheFolder, fileNameInCache + "." + audioFormat.toLowerCase());
63 if (audioFileInCache.exists()) {
64 return audioFileInCache;
67 // if not in cache, get audio data and put to cache
68 try (InputStream is = super.getTextToSpeech(apiKey, text, locale, voice, audioFormat);
69 FileOutputStream fos = new FileOutputStream(audioFileInCache)) {
71 // write text to file for transparency too
72 // this allows to know which contents is in which audio file
73 File txtFileInCache = new File(cacheFolder, fileNameInCache + ".txt");
74 writeText(txtFileInCache, text);
76 return audioFileInCache;
77 } catch (FileNotFoundException ex) {
78 logger.warn("Could not write {} to cache", audioFileInCache, ex);
80 } catch (IOException ex) {
81 logger.error("Could not write {} to cache", audioFileInCache, ex);
87 * Gets a unique filename for a give text, by creating a MD5 hash of it. It
88 * will be preceded by the locale.
90 * Sample: "en-US_00a2653ac5f77063bc4ea2fee87318d3"
92 private String getUniqueFilenameForText(String text, String locale, String voice) {
94 byte[] bytesOfMessage = text.getBytes(StandardCharsets.UTF_8);
95 MessageDigest md = MessageDigest.getInstance("MD5");
96 byte[] md5Hash = md.digest(bytesOfMessage);
97 BigInteger bigInt = new BigInteger(1, md5Hash);
98 String hashtext = bigInt.toString(16);
99 // Now we need to zero pad it if you actually want the full 32
101 while (hashtext.length() < 32) {
102 hashtext = "0" + hashtext;
104 String filename = locale + "_";
105 if (!DEFAULT_VOICE.equals(voice)) {
106 filename += voice + "_";
108 filename += hashtext;
110 } catch (NoSuchAlgorithmException ex) {
112 logger.error("Could not create MD5 hash for '{}'", text, ex);
119 private void copyStream(InputStream inputStream, OutputStream outputStream) throws IOException {
120 byte[] bytes = new byte[READ_BUFFER_SIZE];
121 int read = inputStream.read(bytes, 0, READ_BUFFER_SIZE);
123 outputStream.write(bytes, 0, read);
124 read = inputStream.read(bytes, 0, READ_BUFFER_SIZE);
128 private void writeText(File file, String text) throws IOException {
129 try (OutputStream outputStream = new FileOutputStream(file)) {
130 outputStream.write(text.getBytes(StandardCharsets.UTF_8));