]> git.basschouten.com Git - openhab-addons.git/blob
39662249e1b915af5becc35ec2dac1758fc04f30
[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.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;
26
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 /**
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.
34  *
35  * @author Jochen Hiller - Initial contribution
36  */
37 public class CachedVoiceRSSCloudImpl extends VoiceRSSCloudImpl {
38
39     private final Logger logger = LoggerFactory.getLogger(CachedVoiceRSSCloudImpl.class);
40
41     private final File cacheFolder;
42
43     /**
44      * Stream buffer size
45      */
46     private static final int READ_BUFFER_SIZE = 4096;
47
48     public CachedVoiceRSSCloudImpl(String cacheFolderName) {
49         if (cacheFolderName == null) {
50             throw new IllegalStateException("Folder for cache must be defined");
51         }
52         // Lazy create the cache folder
53         cacheFolder = new File(cacheFolderName);
54         if (!cacheFolder.exists()) {
55             cacheFolder.mkdirs();
56         }
57     }
58
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);
62         // check if in cache
63         File audioFileInCache = new File(cacheFolder, fileNameInCache + "." + audioCodec.toLowerCase());
64         if (audioFileInCache.exists()) {
65             return audioFileInCache;
66         }
67
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)) {
71             copyStream(is, fos);
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);
76             // return from cache
77             return audioFileInCache;
78         } catch (FileNotFoundException ex) {
79             logger.warn("Could not write {} to cache", audioFileInCache, ex);
80             return null;
81         } catch (IOException ex) {
82             logger.error("Could not write {} to cache", audioFileInCache, ex);
83             return null;
84         }
85     }
86
87     /**
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".
91      *
92      * Sample: "en-US_00a2653ac5f77063bc4ea2fee87318d3"
93      */
94     private String getUniqueFilenameForText(String text, String locale, String voice, String format) {
95         try {
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
102             // chars.
103             while (hashtext.length() < 32) {
104                 hashtext = "0" + hashtext;
105             }
106             String filename = locale + "_";
107             if (!DEFAULT_VOICE.equals(voice)) {
108                 filename += voice + "_";
109             }
110             filename += hashtext;
111             if (!Objects.equals(format, "44khz_16bit_mono")) {
112                 filename += "_" + format;
113             }
114             return filename;
115         } catch (NoSuchAlgorithmException ex) {
116             // should not happen
117             logger.error("Could not create MD5 hash for '{}'", text, ex);
118             return null;
119         }
120     }
121
122     // helper methods
123
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);
127         while (read > 0) {
128             outputStream.write(bytes, 0, read);
129             read = inputStream.read(bytes, 0, READ_BUFFER_SIZE);
130         }
131     }
132
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));
136         }
137     }
138 }