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.FileOutputStream;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.io.OutputStream;
20 import java.math.BigInteger;
21 import java.nio.charset.StandardCharsets;
22 import java.security.MessageDigest;
23 import java.security.NoSuchAlgorithmException;
24 import java.util.Objects;
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 {
41 private static final int READ_BUFFER_SIZE = 4096;
43 private final Logger logger = LoggerFactory.getLogger(CachedVoiceRSSCloudImpl.class);
45 private final File cacheFolder;
47 public CachedVoiceRSSCloudImpl(String cacheFolderName) throws IllegalStateException {
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 audioCodec,
59 String audioFormat) throws IOException {
60 String fileNameInCache = getUniqueFilenameForText(text, locale, voice, audioFormat);
61 if (fileNameInCache == null) {
62 throw new IOException("Could not infer cache file name");
65 File audioFileInCache = new File(cacheFolder, fileNameInCache + "." + audioCodec.toLowerCase());
66 if (audioFileInCache.exists()) {
67 return audioFileInCache;
70 // if not in cache, get audio data and put to cache
71 try (InputStream is = super.getTextToSpeech(apiKey, text, locale, voice, audioCodec, audioFormat);
72 FileOutputStream fos = new FileOutputStream(audioFileInCache)) {
74 // write text to file for transparency too
75 // this allows to know which contents is in which audio file
76 File txtFileInCache = new File(cacheFolder, fileNameInCache + ".txt");
77 writeText(txtFileInCache, text);
79 return audioFileInCache;
80 } catch (IOException ex) {
81 throw new IOException("Could not write to cache file: " + ex.getMessage(), ex);
86 * Gets a unique filename for a give text, by creating a MD5 hash of it. It
87 * will be preceded by the locale and suffixed by the format if it is not the
88 * default of "44khz_16bit_mono".
90 * Sample: "en-US_00a2653ac5f77063bc4ea2fee87318d3"
92 private String getUniqueFilenameForText(String text, String locale, String voice, String format) {
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;
109 if (!Objects.equals(format, "44khz_16bit_mono")) {
110 filename += "_" + format;
113 } catch (NoSuchAlgorithmException ex) {
115 logger.error("Could not create MD5 hash for '{}'", text, ex);
122 private void copyStream(InputStream inputStream, OutputStream outputStream) throws IOException {
123 byte[] bytes = new byte[READ_BUFFER_SIZE];
124 int read = inputStream.read(bytes, 0, READ_BUFFER_SIZE);
126 outputStream.write(bytes, 0, read);
127 read = inputStream.read(bytes, 0, READ_BUFFER_SIZE);
131 private void writeText(File file, String text) throws IOException {
132 try (OutputStream outputStream = new FileOutputStream(file)) {
133 outputStream.write(text.getBytes(StandardCharsets.UTF_8));