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;
25 import java.util.Objects;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
31 * This class implements a cache for the retrieved audio data. It will preserve
32 * them in file system, as audio files with an additional .txt file to indicate
33 * what content is in the audio file.
35 * @author Jochen Hiller - Initial contribution
37 public class CachedVoiceRSSCloudImpl extends VoiceRSSCloudImpl {
39 private final Logger logger = LoggerFactory.getLogger(CachedVoiceRSSCloudImpl.class);
41 private final File cacheFolder;
46 private static final int READ_BUFFER_SIZE = 4096;
48 public CachedVoiceRSSCloudImpl(String cacheFolderName) {
49 if (cacheFolderName == null) {
50 throw new IllegalStateException("Folder for cache must be defined");
52 // Lazy create the cache folder
53 cacheFolder = new File(cacheFolderName);
54 if (!cacheFolder.exists()) {
59 public File getTextToSpeechAsFile(String apiKey, String text, String locale, String voice, String audioCodec,
60 String audioFormat) throws IOException {
61 String fileNameInCache = getUniqueFilenameForText(text, locale, voice, audioFormat);
63 File audioFileInCache = new File(cacheFolder, fileNameInCache + "." + audioCodec.toLowerCase());
64 if (audioFileInCache.exists()) {
65 return audioFileInCache;
68 // if not in cache, get audio data and put to cache
69 try (InputStream is = super.getTextToSpeech(apiKey, text, locale, voice, audioCodec, audioFormat);
70 FileOutputStream fos = new FileOutputStream(audioFileInCache)) {
72 // write text to file for transparency too
73 // this allows to know which contents is in which audio file
74 File txtFileInCache = new File(cacheFolder, fileNameInCache + ".txt");
75 writeText(txtFileInCache, text);
77 return audioFileInCache;
78 } catch (FileNotFoundException ex) {
79 logger.warn("Could not write {} to cache", audioFileInCache, ex);
81 } catch (IOException ex) {
82 logger.error("Could not write {} to cache", audioFileInCache, ex);
88 * Gets a unique filename for a give text, by creating a MD5 hash of it. It
89 * will be preceded by the locale and suffixed by the format if it is not the
90 * default of "44khz_16bit_mono".
92 * Sample: "en-US_00a2653ac5f77063bc4ea2fee87318d3"
94 private String getUniqueFilenameForText(String text, String locale, String voice, String format) {
96 byte[] bytesOfMessage = text.getBytes(StandardCharsets.UTF_8);
97 MessageDigest md = MessageDigest.getInstance("MD5");
98 byte[] md5Hash = md.digest(bytesOfMessage);
99 BigInteger bigInt = new BigInteger(1, md5Hash);
100 String hashtext = bigInt.toString(16);
101 // Now we need to zero pad it if you actually want the full 32
103 while (hashtext.length() < 32) {
104 hashtext = "0" + hashtext;
106 String filename = locale + "_";
107 if (!DEFAULT_VOICE.equals(voice)) {
108 filename += voice + "_";
110 filename += hashtext;
111 if (!Objects.equals(format, "44khz_16bit_mono")) {
112 filename += "_" + format;
115 } catch (NoSuchAlgorithmException ex) {
117 logger.error("Could not create MD5 hash for '{}'", text, ex);
124 private void copyStream(InputStream inputStream, OutputStream outputStream) throws IOException {
125 byte[] bytes = new byte[READ_BUFFER_SIZE];
126 int read = inputStream.read(bytes, 0, READ_BUFFER_SIZE);
128 outputStream.write(bytes, 0, read);
129 read = inputStream.read(bytes, 0, READ_BUFFER_SIZE);
133 private void writeText(File file, String text) throws IOException {
134 try (OutputStream outputStream = new FileOutputStream(file)) {
135 outputStream.write(text.getBytes(StandardCharsets.UTF_8));