2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.voice.picotts.internal;
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;
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;
32 * Implementation of {@link AudioStream} for {@link PicoTTSService}
34 * @author Florian Schmidt - Initial Contribution
37 class PicoTTSAudioStream extends FixedLengthAudioStream implements Disposable {
39 private final Voice voice;
40 private final String text;
41 private final AudioFormat audioFormat;
42 private final InputStream inputStream;
45 private @Nullable File file;
47 public PicoTTSAudioStream(String text, Voice voice, AudioFormat audioFormat) throws AudioException {
50 this.audioFormat = audioFormat;
51 this.inputStream = createInputStream();
55 public AudioFormat getFormat() {
59 private InputStream createInputStream() throws AudioException {
60 String outputFile = generateOutputFilename();
61 String[] command = getCommand(outputFile);
64 Process process = Runtime.getRuntime().exec(command);
66 File file = new File(outputFile);
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);
77 private InputStream getFileInputStream(File file) throws AudioException {
80 return new FileInputStream(file);
81 } catch (FileNotFoundException e) {
82 throw new AudioException("Cannot open temporary audio file '" + file.getName() + ".");
85 throw new AudioException("Temporary file '" + file.getName() + "' not found!");
90 * Generates a unique, absolute output filename
92 * @return Unique, absolute output filename
94 private String generateOutputFilename() throws AudioException {
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);
105 * Gets the command used to generate an audio file {@code outputFile}
107 * @param outputFile The absolute filename of the command's output
108 * @return The command used to generate the audio file {@code outputFile}
110 private String[] getCommand(String outputFile) {
111 return new String[] { "pico2wave", "-l=" + this.voice.getLabel(), "-w=" + outputFile, this.text };
115 public int read() throws IOException {
116 return inputStream.read();
120 public long length() {
125 public InputStream getClonedStream() throws AudioException {
126 File file = this.file;
128 return getFileInputStream(file);
130 throw new AudioException("No temporary audio file available.");
135 public void dispose() throws IOException {
136 File localFile = file;
137 if (localFile != null && localFile.exists()) {
139 if (!localFile.delete()) {
140 throw new IOException("Failed to delete the file " + localFile.getAbsolutePath());
142 } catch (SecurityException e) {
143 throw new IOException("Failed to delete the file " + localFile.getAbsolutePath(), e);