]> git.basschouten.com Git - openhab-addons.git/blob
5d0c5288323a39837eceb43b4735f88910a286ee
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.picotts.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 import java.nio.file.Files;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.core.audio.AudioException;
25 import org.openhab.core.audio.AudioFormat;
26 import org.openhab.core.audio.AudioStream;
27 import org.openhab.core.audio.FixedLengthAudioStream;
28 import org.openhab.core.common.Disposable;
29 import org.openhab.core.voice.Voice;
30
31 /**
32  * Implementation of {@link AudioStream} for {@link PicoTTSService}
33  *
34  * @author Florian Schmidt - Initial Contribution
35  */
36 @NonNullByDefault
37 class PicoTTSAudioStream extends FixedLengthAudioStream implements Disposable {
38
39     private final Voice voice;
40     private final String text;
41     private final AudioFormat audioFormat;
42     private final InputStream inputStream;
43
44     private long length;
45     private @Nullable File file;
46
47     public PicoTTSAudioStream(String text, Voice voice, AudioFormat audioFormat) throws AudioException {
48         this.text = text;
49         this.voice = voice;
50         this.audioFormat = audioFormat;
51         this.inputStream = createInputStream();
52     }
53
54     @Override
55     public AudioFormat getFormat() {
56         return audioFormat;
57     }
58
59     private InputStream createInputStream() throws AudioException {
60         String outputFile = generateOutputFilename();
61         String[] command = getCommand(outputFile);
62
63         try {
64             Process process = Runtime.getRuntime().exec(command);
65             process.waitFor();
66             File file = new File(outputFile);
67             this.file = file;
68             this.length = file.length();
69             return getFileInputStream(file);
70         } catch (IOException e) {
71             throw new AudioException("Error while executing '" + command + "'", e);
72         } catch (InterruptedException e) {
73             throw new AudioException("The '" + command + "' has been interrupted", e);
74         }
75     }
76
77     private InputStream getFileInputStream(File file) throws AudioException {
78         if (file.exists()) {
79             try {
80                 return new FileInputStream(file);
81             } catch (FileNotFoundException e) {
82                 throw new AudioException("Cannot open temporary audio file '" + file.getName() + ".");
83             }
84         } else {
85             throw new AudioException("Temporary file '" + file.getName() + "' not found!");
86         }
87     }
88
89     /**
90      * Generates a unique, absolute output filename
91      *
92      * @return Unique, absolute output filename
93      */
94     private String generateOutputFilename() throws AudioException {
95         try {
96             File tempFile = Files.createTempFile(Integer.toString(text.hashCode()), ".wav").toFile();
97             tempFile.deleteOnExit();
98             return tempFile.getAbsolutePath();
99         } catch (IOException e) {
100             throw new AudioException("Unable to create temp file.", e);
101         }
102     }
103
104     /**
105      * Gets the command used to generate an audio file {@code outputFile}
106      *
107      * @param outputFile The absolute filename of the command's output
108      * @return The command used to generate the audio file {@code outputFile}
109      */
110     private String[] getCommand(String outputFile) {
111         return new String[] { "pico2wave", "-l=" + this.voice.getLabel(), "-w=" + outputFile, this.text };
112     }
113
114     @Override
115     public int read() throws IOException {
116         return inputStream.read();
117     }
118
119     @Override
120     public long length() {
121         return length;
122     }
123
124     @Override
125     public InputStream getClonedStream() throws AudioException {
126         File file = this.file;
127         if (file != null) {
128             return getFileInputStream(file);
129         } else {
130             throw new AudioException("No temporary audio file available.");
131         }
132     }
133
134     @Override
135     public void dispose() throws IOException {
136         File localFile = file;
137         if (localFile != null && localFile.exists()) {
138             try {
139                 if (!localFile.delete()) {
140                     throw new IOException("Failed to delete the file " + localFile.getAbsolutePath());
141                 }
142             } catch (SecurityException e) {
143                 throw new IOException("Failed to delete the file " + localFile.getAbsolutePath(), e);
144             }
145         }
146     }
147 }