]> git.basschouten.com Git - openhab-addons.git/blob
fd4977f2f4cfaafe1269e1526815a5a40a02fd8e
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.voice.voicerss.internal.cloudapi;
14
15 import java.io.File;
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;
25
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 /**
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.
33  *
34  * @author Jochen Hiller - Initial contribution
35  */
36 public class CachedVoiceRSSCloudImpl extends VoiceRSSCloudImpl {
37
38     /**
39      * Stream buffer size
40      */
41     private static final int READ_BUFFER_SIZE = 4096;
42
43     private final Logger logger = LoggerFactory.getLogger(CachedVoiceRSSCloudImpl.class);
44
45     private final File cacheFolder;
46
47     public CachedVoiceRSSCloudImpl(String cacheFolderName) throws IllegalStateException {
48         if (cacheFolderName == null) {
49             throw new IllegalStateException("Folder for cache must be defined");
50         }
51         // Lazy create the cache folder
52         cacheFolder = new File(cacheFolderName);
53         if (!cacheFolder.exists()) {
54             cacheFolder.mkdirs();
55         }
56     }
57
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");
63         }
64         // check if in cache
65         File audioFileInCache = new File(cacheFolder, fileNameInCache + "." + audioCodec.toLowerCase());
66         if (audioFileInCache.exists()) {
67             return audioFileInCache;
68         }
69
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)) {
73             copyStream(is, fos);
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);
78             // return from cache
79             return audioFileInCache;
80         } catch (IOException ex) {
81             throw new IOException("Could not write to cache file: " + ex.getMessage(), ex);
82         }
83     }
84
85     /**
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".
89      *
90      * Sample: "en-US_00a2653ac5f77063bc4ea2fee87318d3"
91      */
92     private String getUniqueFilenameForText(String text, String locale, String voice, String format) {
93         try {
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
100             // chars.
101             while (hashtext.length() < 32) {
102                 hashtext = "0" + hashtext;
103             }
104             String filename = locale + "_";
105             if (!DEFAULT_VOICE.equals(voice)) {
106                 filename += voice + "_";
107             }
108             filename += hashtext;
109             if (!Objects.equals(format, "44khz_16bit_mono")) {
110                 filename += "_" + format;
111             }
112             return filename;
113         } catch (NoSuchAlgorithmException ex) {
114             // should not happen
115             logger.error("Could not create MD5 hash for '{}'", text, ex);
116             return null;
117         }
118     }
119
120     // helper methods
121
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);
125         while (read > 0) {
126             outputStream.write(bytes, 0, read);
127             read = inputStream.read(bytes, 0, READ_BUFFER_SIZE);
128         }
129     }
130
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));
134         }
135     }
136 }