added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.voice.marytts-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-voice-marytts" description="Mary Text-to-Speech" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle dependency="true">mvn:commons-collections/commons-collections/3.2.2</bundle>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.voice.marytts/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,161 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.voice.marytts.internal;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import javax.sound.sampled.AudioInputStream;
import org.apache.commons.io.IOUtils;
import org.openhab.core.audio.AudioException;
import org.openhab.core.audio.AudioFormat;
import org.openhab.core.audio.AudioSource;
import org.openhab.core.audio.FixedLengthAudioStream;
/**
* Implementation of the {@link AudioSource} interface for the {@link MaryTTSService}
*
* @author Kelly Davis - Initial contribution and API
* @author Kai Kreuzer - Refactored to updated APIs and moved to openHAB
*/
class MaryTTSAudioStream extends FixedLengthAudioStream {
/**
* {@link AudioFormat} of this {@link AudioSource}
*/
private final AudioFormat audioFormat;
/**
* {@link InputStream} of this {@link AudioSource}
*/
private InputStream inputStream;
private final byte[] rawAudio;
private final int length;
/**
* Constructs an instance with the passed properties
*
* @param inputStream The InputStream of this instance
* @param audioFormat The AudioFormat of this instance
* @throws IOException
*/
public MaryTTSAudioStream(AudioInputStream inputStream, AudioFormat audioFormat) throws IOException {
rawAudio = IOUtils.toByteArray(inputStream);
this.length = rawAudio.length + 36;
this.inputStream = new SequenceInputStream(getWavHeaderInputStream(length), new ByteArrayInputStream(rawAudio));
this.audioFormat = audioFormat;
}
@Override
public AudioFormat getFormat() {
return this.audioFormat;
}
@Override
public int read(byte[] b) throws IOException {
return inputStream.read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return inputStream.read(b, off, len);
}
@Override
public int read() throws IOException {
return inputStream.read();
}
@Override
public long length() {
return length;
}
private InputStream getWavHeaderInputStream(int length) throws IOException {
// WAVE header
// see http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
byte[] header = new byte[44];
byte format = 0x10; // PCM
byte bits = 16;
byte channel = 1;
long srate = (this.audioFormat != null) ? this.audioFormat.getFrequency() : 48000l;
long rawLength = length - 36;
long bitrate = srate * channel * bits;
header[0] = 'R';
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (byte) (length & 0xff);
header[5] = (byte) ((length >> 8) & 0xff);
header[6] = (byte) ((length >> 16) & 0xff);
header[7] = (byte) ((length >> 24) & 0xff);
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
header[12] = 'f';
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
header[16] = format;
header[17] = 0;
header[18] = 0;
header[19] = 0;
header[20] = 1;
header[21] = 0;
header[22] = channel;
header[23] = 0;
header[24] = (byte) (srate & 0xff);
header[25] = (byte) ((srate >> 8) & 0xff);
header[26] = (byte) ((srate >> 16) & 0xff);
header[27] = (byte) ((srate >> 24) & 0xff);
header[28] = (byte) ((bitrate / 8) & 0xff);
header[29] = (byte) (((bitrate / 8) >> 8) & 0xff);
header[30] = (byte) (((bitrate / 8) >> 16) & 0xff);
header[31] = (byte) (((bitrate / 8) >> 24) & 0xff);
header[32] = (byte) ((channel * bits) / 8);
header[33] = 0;
header[34] = 16;
header[35] = 0;
header[36] = 'd';
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (rawLength & 0xff);
header[41] = (byte) ((rawLength >> 8) & 0xff);
header[42] = (byte) ((rawLength >> 16) & 0xff);
header[43] = (byte) ((rawLength >> 24) & 0xff);
return new ByteArrayInputStream(header);
}
@Override
public synchronized void reset() throws IOException {
IOUtils.closeQuietly(inputStream);
this.inputStream = new SequenceInputStream(getWavHeaderInputStream(length), new ByteArrayInputStream(rawAudio));
}
@Override
public InputStream getClonedStream() throws AudioException {
try {
return new SequenceInputStream(getWavHeaderInputStream(length), new ByteArrayInputStream(rawAudio));
} catch (IOException e) {
throw new AudioException(e);
}
}
}

View File

@@ -0,0 +1,192 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.voice.marytts.internal;
import static javax.sound.sampled.AudioSystem.NOT_SPECIFIED;
import java.io.IOException;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import org.openhab.core.audio.AudioFormat;
import org.openhab.core.audio.AudioStream;
import org.openhab.core.voice.TTSException;
import org.openhab.core.voice.TTSService;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import marytts.LocalMaryInterface;
import marytts.MaryInterface;
import marytts.exceptions.MaryConfigurationException;
import marytts.exceptions.SynthesisException;
import marytts.modules.synthesis.Voice;
/**
* This is a TTS service implementation for using MaryTTS.
*
* @author Kelly Davis - Initial contribution and API
* @author Kai Kreuzer - Refactored to updated APIs and moved to openHAB
*/
@Component
public class MaryTTSService implements TTSService {
private final Logger logger = LoggerFactory.getLogger(MaryTTSService.class);
private MaryInterface marytts;
/**
* Set of supported voices
*/
private Set<org.openhab.core.voice.Voice> voices;
/**
* Set of supported audio formats
*/
private Set<AudioFormat> audioFormats;
protected void activate() {
try {
marytts = new LocalMaryInterface();
voices = initVoices();
audioFormats = initAudioFormats();
} catch (MaryConfigurationException e) {
logger.error("Failed to initialize MaryTTS: {}", e.getMessage(), e);
}
}
@Override
public Set<org.openhab.core.voice.Voice> getAvailableVoices() {
return voices;
}
@Override
public Set<AudioFormat> getSupportedFormats() {
return audioFormats;
}
@Override
public AudioStream synthesize(String text, org.openhab.core.voice.Voice voice, AudioFormat requestedFormat)
throws TTSException {
// Validate arguments
if (text == null || text.isEmpty()) {
throw new TTSException("The passed text is null or empty");
}
if (!voices.contains(voice)) {
throw new TTSException("The passed voice is unsupported");
}
if (audioFormats.stream().noneMatch(f -> f.isCompatible(requestedFormat))) {
throw new TTSException("The passed AudioFormat is unsupported");
}
/*
* NOTE: For each MaryTTS voice only a single AudioFormat is supported
* However, the TTSService interface allows the AudioFormat and
* the Voice to vary independently. Thus, an external user does
* not know about the requirement that a given voice is paired
* with a given AudioFormat. The test below enforces this.
*
* However, this leads to a problem. The user has no way to
* know which AudioFormat is apropos for a give Voice. Thus,
* throwing a TTSException for the wrong AudioFormat makes
* the user guess the right AudioFormat, a painful process.
* Alternatively, we can get the right AudioFormat for the
* Voice and ignore what the user requests, also wrong.
*
* TODO: Decide what to do
* Voice maryTTSVoice = Voice.getVoice(voice.getLabel());
* AudioFormat maryTTSVoiceAudioFormat = getAudioFormat(maryTTSVoice.dbAudioFormat());
* if (!maryTTSVoiceAudioFormat.isCompatible(requestedFormat)) {
* throw new TTSException("The passed AudioFormat is incompatable with the voice");
* }
*/
Voice maryTTSVoice = Voice.getVoice(voice.getLabel());
AudioFormat maryTTSVoiceAudioFormat = getAudioFormat(maryTTSVoice.dbAudioFormat());
// Synchronize on marytts
synchronized (marytts) {
// Set voice (Each voice supports only a single AudioFormat)
marytts.setLocale(voice.getLocale());
marytts.setVoice(voice.getLabel());
try {
return new MaryTTSAudioStream(marytts.generateAudio(text), maryTTSVoiceAudioFormat);
} catch (SynthesisException | IOException e) {
throw new TTSException("Error generating an AudioStream", e);
}
}
}
/**
* Initializes voices
*
* @return The voices of this instance
*/
private Set<org.openhab.core.voice.Voice> initVoices() {
Set<org.openhab.core.voice.Voice> voices = new HashSet<>();
for (Locale locale : marytts.getAvailableLocales()) {
for (String voiceLabel : marytts.getAvailableVoices(locale)) {
voices.add(new MaryTTSVoice(locale, voiceLabel));
}
}
return voices;
}
/**
* Initializes audioFormats
*
* @return The audio formats of this instance
*/
private Set<AudioFormat> initAudioFormats() {
Set<AudioFormat> audioFormats = new HashSet<>();
for (String voiceLabel : marytts.getAvailableVoices()) {
audioFormats.add(getAudioFormat(Voice.getVoice(voiceLabel).dbAudioFormat()));
}
return audioFormats;
}
/**
* Obtains an AudioFormat from a javax.sound.sampled.AudioFormat
*
* @param audioFormat The javax.sound.sampled.AudioFormat
* @return The corresponding AudioFormat
*/
private AudioFormat getAudioFormat(javax.sound.sampled.AudioFormat audioFormat) {
String container = AudioFormat.CONTAINER_WAVE;
String codec = audioFormat.getEncoding().toString();
Boolean bigEndian = audioFormat.isBigEndian();
int frameSize = audioFormat.getFrameSize(); // In bytes
int bitsPerFrame = frameSize * 8;
Integer bitDepth = NOT_SPECIFIED == frameSize ? null : bitsPerFrame;
float frameRate = audioFormat.getFrameRate();
Integer bitRate = NOT_SPECIFIED == frameRate ? null : (int) frameRate * bitsPerFrame;
float sampleRate = audioFormat.getSampleRate();
Long frequency = NOT_SPECIFIED == sampleRate ? null : (long) sampleRate;
return new AudioFormat(container, codec, bigEndian, bitDepth, bitRate, frequency);
}
@Override
public String getId() {
return "marytts";
}
@Override
public String getLabel(Locale locale) {
return "MaryTTS";
}
}

View File

@@ -0,0 +1,75 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.voice.marytts.internal;
import java.util.Locale;
import org.openhab.core.voice.Voice;
/**
* Implementation of the Voice interface for MaryTTS
*
* @author Kelly Davis - Initial contribution and API
* @author Kai Kreuzer - Refactored to updated APIs and moved to openHAB
*/
public class MaryTTSVoice implements Voice {
/**
* Voice locale
*/
private final Locale locale;
/**
* Voice label
*/
private final String label;
/**
* Constructs a MaryTTS Voice for the passed data
*
* @param locale The Locale of the voice
* @param label The label of the voice
*/
public MaryTTSVoice(Locale locale, String label) {
this.locale = locale;
this.label = label;
}
/**
* Globally unique identifier of the voice.
*
* @return A String uniquely identifying the voice globally
*/
@Override
public String getUID() {
return "marytts:" + label.replaceAll("[^a-zA-Z0-9_]", "");
}
/**
* The voice label, used for GUI's or VUI's
*
* @return The voice label, may not be globally unique
*/
@Override
public String getLabel() {
return label;
}
/**
* @inheritDoc
*/
@Override
public Locale getLocale() {
return locale;
}
}

View File

@@ -0,0 +1,6 @@
marytts.config.MainConfig
marytts.language.de.GermanConfig
marytts.language.en.EnglishConfig
marytts.voice.Bits1Hsmm.Config
marytts.voice.Bits3Hsmm.Config
marytts.voice.CmuSltHsmm.Config