]> git.basschouten.com Git - openhab-addons.git/blob
b94f07cf4793ae710e995b8f6f522cab71d9a329
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.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
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     private final Logger logger = LoggerFactory.getLogger(CachedVoiceRSSCloudImpl.class);
39
40     private final File cacheFolder;
41
42     /**
43      * Stream buffer size
44      */
45     private static final int READ_BUFFER_SIZE = 4096;
46
47     public CachedVoiceRSSCloudImpl(String cacheFolderName) {
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 audioFormat)
59             throws IOException {
60         String fileNameInCache = getUniqueFilenameForText(text, locale, voice);
61         // check if in cache
62         File audioFileInCache = new File(cacheFolder, fileNameInCache + "." + audioFormat.toLowerCase());
63         if (audioFileInCache.exists()) {
64             return audioFileInCache;
65         }
66
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)) {
70             copyStream(is, fos);
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);
75             // return from cache
76             return audioFileInCache;
77         } catch (FileNotFoundException ex) {
78             logger.warn("Could not write {} to cache", audioFileInCache, ex);
79             return null;
80         } catch (IOException ex) {
81             logger.error("Could not write {} to cache", audioFileInCache, ex);
82             return null;
83         }
84     }
85
86     /**
87      * Gets a unique filename for a give text, by creating a MD5 hash of it. It
88      * will be preceded by the locale.
89      *
90      * Sample: "en-US_00a2653ac5f77063bc4ea2fee87318d3"
91      */
92     private String getUniqueFilenameForText(String text, String locale, String voice) {
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             return filename;
110         } catch (NoSuchAlgorithmException ex) {
111             // should not happen
112             logger.error("Could not create MD5 hash for '{}'", text, ex);
113             return null;
114         }
115     }
116
117     // helper methods
118
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);
122         while (read > 0) {
123             outputStream.write(bytes, 0, read);
124             read = inputStream.read(bytes, 0, READ_BUFFER_SIZE);
125         }
126     }
127
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));
131         }
132     }
133 }