]> git.basschouten.com Git - openhab-addons.git/blob
ad301a783bc5b5c627aee8b11c04648d52f0bd91
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.mactts.internal;
14
15 import java.io.File;
16 import java.io.FileInputStream;
17 import java.io.FileNotFoundException;
18 import java.io.IOException;
19 import java.io.InputStream;
20
21 import org.openhab.core.audio.AudioException;
22 import org.openhab.core.audio.AudioFormat;
23 import org.openhab.core.audio.AudioStream;
24 import org.openhab.core.audio.FixedLengthAudioStream;
25 import org.openhab.core.voice.Voice;
26
27 /**
28  * Implementation of the {@link AudioStream} interface for the {@link MacTTSService}
29  *
30  * @author Kelly Davis - Initial contribution and API
31  * @author Kai Kreuzer - Refactored to use AudioStream and fixed audio format to produce
32  */
33 class MacTTSAudioStream extends FixedLengthAudioStream {
34
35     /**
36      * {@link Voice} this {@link AudioStream} speaks in
37      */
38     private final Voice voice;
39
40     /**
41      * Text spoken in this {@link AudioStream}
42      */
43     private final String text;
44
45     /**
46      * {@link AudioFormat} of this {@link AudioStream}
47      */
48     private final AudioFormat audioFormat;
49
50     /**
51      * The raw input stream
52      */
53     private InputStream inputStream;
54
55     private long length;
56     private File file;
57
58     /**
59      * Constructs an instance with the passed properties.
60      *
61      * It is assumed that the passed properties have been validated.
62      *
63      * @param text The text spoken in this {@link AudioStream}
64      * @param voice The {@link Voice} used to speak this instance's text
65      * @param audioFormat The {@link AudioFormat} of this {@link AudioStream}
66      * @throws AudioException if stream cannot be created
67      */
68     public MacTTSAudioStream(String text, Voice voice, AudioFormat audioFormat) throws AudioException {
69         this.text = text;
70         this.voice = voice;
71         this.audioFormat = audioFormat;
72         this.inputStream = createInputStream();
73     }
74
75     @Override
76     public AudioFormat getFormat() {
77         return audioFormat;
78     }
79
80     private InputStream createInputStream() throws AudioException {
81         String outputFile = generateOutputFilename();
82         String command = getCommand(outputFile);
83
84         try {
85             Process process = Runtime.getRuntime().exec(command);
86             process.waitFor();
87             file = new File(outputFile);
88             this.length = file.length();
89             return getFileInputStream(file);
90         } catch (IOException e) {
91             throw new AudioException("Error while executing '" + command + "'", e);
92         } catch (InterruptedException e) {
93             throw new AudioException("The '" + command + "' has been interrupted", e);
94         }
95     }
96
97     private InputStream getFileInputStream(File file) throws AudioException {
98         if (file == null) {
99             throw new IllegalArgumentException("file must not be null");
100         }
101         if (file.exists()) {
102             try {
103                 return new FileInputStream(file);
104             } catch (FileNotFoundException e) {
105                 throw new AudioException("Cannot open temporary audio file '" + file.getName() + ".");
106             }
107         } else {
108             throw new AudioException("Temporary file '" + file.getName() + "' not found!");
109         }
110     }
111
112     /**
113      * Generates a unique, absolute output filename
114      *
115      * @return Unique, absolute output filename
116      */
117     private String generateOutputFilename() throws AudioException {
118         File tempFile;
119         try {
120             tempFile = File.createTempFile(Integer.toString(text.hashCode()), ".wav");
121             tempFile.deleteOnExit();
122         } catch (IOException e) {
123             throw new AudioException("Unable to create temp file.", e);
124         }
125         return tempFile.getAbsolutePath();
126     }
127
128     /**
129      * Gets the command used to generate an audio file {@code outputFile}
130      *
131      * @param outputFile The absolute filename of the command's output
132      * @return The command used to generate the audio file {@code outputFile}
133      */
134     private String getCommand(String outputFile) {
135         StringBuffer stringBuffer = new StringBuffer();
136
137         stringBuffer.append("say");
138
139         stringBuffer.append(" --voice=\"" + this.voice.getLabel() + "\"");
140         stringBuffer.append(" --output-file=" + outputFile);
141         stringBuffer.append(" --file-format=" + this.audioFormat.getContainer());
142         stringBuffer.append(" --data-format=LEI" + audioFormat.getBitDepth() + "@" + audioFormat.getFrequency());
143         stringBuffer.append(" --channels=1"); // Mono
144         stringBuffer.append(" " + this.text);
145
146         return stringBuffer.toString();
147     }
148
149     @Override
150     public int read() throws IOException {
151         return inputStream.read();
152     }
153
154     @Override
155     public long length() {
156         return length;
157     }
158
159     @Override
160     public InputStream getClonedStream() throws AudioException {
161         if (file != null) {
162             return getFileInputStream(file);
163         } else {
164             throw new AudioException("No temporary audio file available.");
165         }
166     }
167 }