/**
* Copyright (c) 2014-2017 by the respective copyright holders.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.smarthome.voice.mactts.internal;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import org.eclipse.smarthome.core.audio.AudioException;
import org.eclipse.smarthome.core.audio.AudioFormat;
import org.eclipse.smarthome.core.audio.AudioStream;
import org.eclipse.smarthome.core.audio.FixedLengthAudioStream;
import org.eclipse.smarthome.core.voice.Voice;
/**
* Implementation of the {@link AudioStream} interface for the {@link MacTTSService}
*
* @author Kelly Davis - Initial contribution and API
* @author Kai Kreuzer - Refactored to use AudioStream and fixed audio format to produce
*/
class MacTTSAudioStream extends FixedLengthAudioStream {
/**
* {@link Voice} this {@link AudioStream} speaks in
*/
private final Voice voice;
/**
* Text spoken in this {@link AudioStream}
*/
private final String text;
/**
* {@link AudioFormat} of this {@link AudioStream}
*/
private final AudioFormat audioFormat;
/**
* The raw input stream
*/
private InputStream inputStream;
private long length;
private File file;
/**
* Constructs an instance with the passed properties.
*
* It is assumed that the passed properties have been validated.
*
* @param text The text spoken in this {@link AudioStream}
* @param voice The {@link Voice} used to speak this instance's text
* @param audioFormat The {@link AudioFormat} of this {@link AudioStream}
* @throws AudioException if stream cannot be created
*/
public MacTTSAudioStream(String text, Voice voice, AudioFormat audioFormat) throws AudioException {
this.text = text;
this.voice = voice;
this.audioFormat = audioFormat;
this.inputStream = createInputStream();
}
@Override
public AudioFormat getFormat() {
return audioFormat;
}
private InputStream createInputStream() throws AudioException {
String outputFile = generateOutputFilename();
String command = getCommand(outputFile);
try {
Process process = Runtime.getRuntime().exec(command);
process.waitFor();
file = new File(outputFile);
this.length = file.length();
return getFileInputStream(file);
} catch (IOException e) {
throw new AudioException("Error while executing '" + command + "'", e);
} catch (InterruptedException e) {
throw new AudioException("The '" + command + "' has been interrupted", e);
}
}
private InputStream getFileInputStream(File file) throws AudioException {
if (file == null) {
throw new IllegalArgumentException("file must not be null");
}
if (file.exists()) {
try {
return new FileInputStream(file);
} catch (FileNotFoundException e) {
throw new AudioException("Cannot open temporary audio file '" + file.getName() + ".");
}
} else {
throw new AudioException("Temporary file '" + file.getName() + "' not found!");
}
}
/**
* Generates a unique, absolute output filename
*
* @return Unique, absolute output filename
*/
private String generateOutputFilename() throws AudioException {
File tempFile;
try {
tempFile = File.createTempFile(Integer.toString(text.hashCode()), ".wav");
tempFile.deleteOnExit();
} catch (IOException e) {
throw new AudioException("Unable to create temp file.", e);
}
return tempFile.getAbsolutePath();
}
/**
* Gets the command used to generate an audio file {@code outputFile}
*
* @param outputFile The absolute filename of the command's output
* @return The command used to generate the audio file {@code outputFile}
*/
private String getCommand(String outputFile) {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("say");
stringBuffer.append(" --voice=" + this.voice.getLabel());
stringBuffer.append(" --output-file=" + outputFile);
stringBuffer.append(" --file-format=" + this.audioFormat.getContainer());
stringBuffer.append(" --data-format=LEI" + audioFormat.getBitDepth() + "@" + audioFormat.getFrequency());
stringBuffer.append(" --channels=1"); // Mono
stringBuffer.append(" " + this.text);
return stringBuffer.toString();
}
@Override
public int read() throws IOException {
return inputStream.read();
}
@Override
public long length() {
return length;
}
@Override
public InputStream getClonedStream() throws AudioException {
if (file != null) {
return getFileInputStream(file);
} else {
throw new AudioException("No temporary audio file available.");
}
}
}