Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[voicerss] Add support for WAV audio format #11916

Merged
merged 2 commits into from
Jan 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ public class VoiceRSSTTSService implements TTSService {

// API Key comes from ConfigAdmin
private static final String CONFIG_API_KEY = "apiKey";

/**
* Map from openHAB AudioFormat Codec to VoiceRSS API Audio Codec
*/
private static final Map<String, String> CODEC_MAP = Map.of(AudioFormat.CODEC_PCM_SIGNED, "WAV",
AudioFormat.CODEC_PCM_UNSIGNED, "WAV", AudioFormat.CODEC_PCM_ALAW, "WAV", AudioFormat.CODEC_PCM_ULAW, "WAV",
AudioFormat.CODEC_MP3, "MP3", AudioFormat.CODEC_VORBIS, "OGG", AudioFormat.CODEC_AAC, "AAC");

/**
* Map from openHAB AudioFormat Frequency to VoiceRSS API Audio Frequency
*/
private static final Map<Long, String> FREQUENCY_MAP = Map.of(8_000L, "8khz", 11_025L, "11khz", 12_000L, "12khz",
16_000L, "16khz", 22_050L, "22khz", 24_000L, "24khz", 32_000L, "32khz", 44_100L, "44khz", 48_000L, "48khz");

private String apiKey;

private final Logger logger = LoggerFactory.getLogger(VoiceRSSTTSService.class);
Expand Down Expand Up @@ -121,22 +135,12 @@ public AudioStream synthesize(String text, Voice voice, AudioFormat requestedFor
if (!voices.contains(voice)) {
throw new TTSException("The passed voice is unsupported");
}
boolean isAudioFormatSupported = false;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check isn't needed here any longer because the methods getApiAudioCodec() and getApiAudioFormat() called later on throw a TTSException if a format isn't supported.

for (AudioFormat currentAudioFormat : audioFormats) {
if (currentAudioFormat.isCompatible(requestedFormat)) {
isAudioFormatSupported = true;
break;
}
}
if (!isAudioFormatSupported) {
throw new TTSException("The passed AudioFormat is unsupported");
}

// now create the input stream for given text, locale, format. There is
// only a default voice
// now create the input stream for given text, locale, voice, codec and format.
try {
File cacheAudioFile = voiceRssImpl.getTextToSpeechAsFile(apiKey, trimmedText,
voice.getLocale().toLanguageTag(), voice.getLabel(), getApiAudioFormat(requestedFormat));
voice.getLocale().toLanguageTag(), voice.getLabel(), getApiAudioCodec(requestedFormat),
getApiAudioFormat(requestedFormat));
if (cacheAudioFile == null) {
throw new TTSException("Could not read from VoiceRSS service");
}
Expand Down Expand Up @@ -169,46 +173,53 @@ private Set<Voice> initVoices() {
* @return The audio formats of this instance
*/
private Set<AudioFormat> initAudioFormats() {
Set<AudioFormat> audioFormats = new HashSet<>();
for (String format : voiceRssImpl.getAvailableAudioFormats()) {
audioFormats.add(getAudioFormat(format));
}
return audioFormats;
return voiceRssImpl.getAvailableAudioFormats();
}

private AudioFormat getAudioFormat(String apiFormat) {
Boolean bigEndian = null;
Integer bitDepth = 16;
Integer bitRate = null;
Long frequency = 44100L;

if ("MP3".equals(apiFormat)) {
// we use by default: MP3, 44khz_16bit_mono with bitrate 64 kbps
bitRate = 64000;
return new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_MP3, bigEndian, bitDepth, bitRate,
frequency);
} else if ("OGG".equals(apiFormat)) {
// we use by default: OGG, 44khz_16bit_mono
return new AudioFormat(AudioFormat.CONTAINER_OGG, AudioFormat.CODEC_VORBIS, bigEndian, bitDepth, bitRate,
frequency);
} else if ("AAC".equals(apiFormat)) {
// we use by default: AAC, 44khz_16bit_mono
return new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_AAC, bigEndian, bitDepth, bitRate,
frequency);
} else {
throw new IllegalArgumentException("Audio format " + apiFormat + " not yet supported");
/**
* Map {@link AudioFormat#getCodec() codec} to VoiceRSS API codec.
*
* @throws TTSException if {@code format} is not supported
*/
private String getApiAudioCodec(AudioFormat format) throws TTSException {
final String internalCodec = format.getCodec();
final String apiCodec = CODEC_MAP.get(internalCodec != null ? internalCodec : AudioFormat.CODEC_PCM_SIGNED);

if (apiCodec == null) {
throw new TTSException("Unsupported audio format: " + format);
}

return apiCodec;
}

private String getApiAudioFormat(AudioFormat format) {
if (format.getCodec().equals(AudioFormat.CODEC_MP3)) {
return "MP3";
} else if (format.getCodec().equals(AudioFormat.CODEC_VORBIS)) {
return "OGG";
} else if (format.getCodec().equals(AudioFormat.CODEC_AAC)) {
return "AAC";
} else {
throw new IllegalArgumentException("Audio format " + format.getCodec() + " not yet supported");
/**
* Map {@link AudioFormat#getBitDepth() bit depth} and {@link AudioFormat#getFrequency() frequency} to VoiceRSS API
* format.
*
* @throws TTSException if {@code format} is not supported
*/
private String getApiAudioFormat(AudioFormat format) throws TTSException {
final int bitDepth = format.getBitDepth() != null ? format.getBitDepth() : 16;
final Long frequency = format.getFrequency() != null ? format.getFrequency() : 44_100L;
final String apiFrequency = FREQUENCY_MAP.get(frequency);

if (apiFrequency == null || (bitDepth != 8 && bitDepth != 16)) {
throw new TTSException("Unsupported audio format: " + format);
}

switch (format.getCodec() != null ? format.getCodec() : AudioFormat.CODEC_PCM_SIGNED) {
case AudioFormat.CODEC_PCM_ALAW:
return "alaw_" + apiFrequency + "_mono";
case AudioFormat.CODEC_PCM_ULAW:
return "ulaw_" + apiFrequency + "_mono";
case AudioFormat.CODEC_PCM_SIGNED:
case AudioFormat.CODEC_PCM_UNSIGNED:
case AudioFormat.CODEC_MP3:
case AudioFormat.CODEC_VORBIS:
case AudioFormat.CODEC_AAC:
return apiFrequency + "_" + bitDepth + "_mono";
default:
throw new TTSException("Unsupported audio format: " + format);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -55,17 +56,17 @@ public CachedVoiceRSSCloudImpl(String cacheFolderName) {
}
}

public File getTextToSpeechAsFile(String apiKey, String text, String locale, String voice, String audioFormat)
throws IOException {
String fileNameInCache = getUniqueFilenameForText(text, locale, voice);
public File getTextToSpeechAsFile(String apiKey, String text, String locale, String voice, String audioCodec,
String audioFormat) throws IOException {
String fileNameInCache = getUniqueFilenameForText(text, locale, voice, audioFormat);
// check if in cache
File audioFileInCache = new File(cacheFolder, fileNameInCache + "." + audioFormat.toLowerCase());
File audioFileInCache = new File(cacheFolder, fileNameInCache + "." + audioCodec.toLowerCase());
if (audioFileInCache.exists()) {
return audioFileInCache;
}

// if not in cache, get audio data and put to cache
try (InputStream is = super.getTextToSpeech(apiKey, text, locale, voice, audioFormat);
try (InputStream is = super.getTextToSpeech(apiKey, text, locale, voice, audioCodec, audioFormat);
FileOutputStream fos = new FileOutputStream(audioFileInCache)) {
copyStream(is, fos);
// write text to file for transparency too
Expand All @@ -85,11 +86,12 @@ public File getTextToSpeechAsFile(String apiKey, String text, String locale, Str

/**
* Gets a unique filename for a give text, by creating a MD5 hash of it. It
* will be preceded by the locale.
* will be preceded by the locale and suffixed by the format if it is not the
* default of "44khz_16bit_mono".
*
* Sample: "en-US_00a2653ac5f77063bc4ea2fee87318d3"
*/
private String getUniqueFilenameForText(String text, String locale, String voice) {
private String getUniqueFilenameForText(String text, String locale, String voice, String format) {
try {
byte[] bytesOfMessage = text.getBytes(StandardCharsets.UTF_8);
MessageDigest md = MessageDigest.getInstance("MD5");
Expand All @@ -106,6 +108,9 @@ private String getUniqueFilenameForText(String text, String locale, String voice
filename += voice + "_";
}
filename += hashtext;
if (!Objects.equals(format, "44khz_16bit_mono")) {
filename += "_" + format;
}
return filename;
} catch (NoSuchAlgorithmException ex) {
// should not happen
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public interface VoiceRSSCloudAPI {
*
* @return A set of all audio formats supported
*/
Set<String> getAvailableAudioFormats();
Set<AudioFormat> getAvailableAudioFormats();

/**
* Get all supported voices.
Expand Down Expand Up @@ -70,13 +70,15 @@ public interface VoiceRSSCloudAPI {
* the locale to use
* @param voice
* the voice to use, "default" for the default voice
* @param audioCodec
* the audio codec to use
* @param audioFormat
* the audio format to use
* @return an InputStream to the audio data in specified format
* @throws IOException
* will be raised if the audio data can not be retrieved from
* cloud service
*/
InputStream getTextToSpeech(String apiKey, String text, String locale, String voice, String audioFormat)
throws IOException;
InputStream getTextToSpeech(String apiKey, String text, String locale, String voice, String audioCodec,
String audioFormat) throws IOException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
*/
package org.openhab.voice.voicerss.internal.cloudapi;

import static java.util.stream.Collectors.toSet;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
Expand All @@ -28,8 +26,8 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Stream;

import org.openhab.core.audio.AudioFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -41,21 +39,51 @@
* <ul>
* <li>All API languages supported</li>
* <li>Only default voice supported with good audio quality</li>
* <li>Only MP3, OGG and AAC audio formats supported</li>
* <li>MP3, OGG, AAC and WAV audio formats supported</li>
* <li>It uses HTTP and not HTTPS (for performance reasons)</li>
* </ul>
*
* @author Jochen Hiller - Initial contribution
* @author Laurent Garnier - add support for all API languages
* @author Laurent Garnier - add support for OGG and AAC audio formats
* @author Andreas Brenk - add support for WAV audio format
*/
public class VoiceRSSCloudImpl implements VoiceRSSCloudAPI {

public static final String DEFAULT_VOICE = "default";

private final Logger logger = LoggerFactory.getLogger(VoiceRSSCloudImpl.class);

private static final Set<String> SUPPORTED_AUDIO_FORMATS = Stream.of("MP3", "OGG", "AAC").collect(toSet());
private static final Set<AudioFormat> SUPPORTED_AUDIO_FORMATS = Set.of(
new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_MP3, null, 16, null, 44_100L),
lolodomo marked this conversation as resolved.
Show resolved Hide resolved
new AudioFormat(AudioFormat.CONTAINER_OGG, AudioFormat.CODEC_VORBIS, null, 16, null, 44_100L),
new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_AAC, null, 16, null, 44_100L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, null, 8, 64_000, 8_000L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, null, 16, 128_000, 8_000L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 88_200, 11_025L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 176_400, 11_025L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 96_000, 12_000L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 192_000, 12_000L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 128_000, 16_000L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 256_000, 16_000L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 176_400, 22_050L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 352_800, 22_050L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 192_000, 24_000L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 384_000, 24_000L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 256_000, 32_000L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 512_000, 32_000L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 352_800, 44_100L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 705_600, 44_100L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 384_000, 48_000L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 768_000, 48_000L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ALAW, null, 8, 64_000, 8_000L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ALAW, null, 8, 88_200, 11_025L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ALAW, null, 8, 176_400, 22_050L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ALAW, null, 8, 352_800, 44_100L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ULAW, null, 8, 64_000, 8_000L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ULAW, null, 8, 88_200, 11_025L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ULAW, null, 8, 176_400, 22_050L),
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ULAW, null, 8, 352_800, 44_100L));

private static final Set<Locale> SUPPORTED_LOCALES = new HashSet<>();
static {
Expand Down Expand Up @@ -164,7 +192,7 @@ public class VoiceRSSCloudImpl implements VoiceRSSCloudAPI {
}

@Override
public Set<String> getAvailableAudioFormats() {
public Set<AudioFormat> getAvailableAudioFormats() {
return SUPPORTED_AUDIO_FORMATS;
}

Expand Down Expand Up @@ -208,9 +236,9 @@ public Set<String> getAvailableVoices(Locale locale) {
* dependencies.
*/
@Override
public InputStream getTextToSpeech(String apiKey, String text, String locale, String voice, String audioFormat)
throws IOException {
String url = createURL(apiKey, text, locale, voice, audioFormat);
public InputStream getTextToSpeech(String apiKey, String text, String locale, String voice, String audioCodec,
String audioFormat) throws IOException {
String url = createURL(apiKey, text, locale, voice, audioCodec, audioFormat);
logger.debug("Call {}", url);
URLConnection connection = new URL(url).openConnection();

Expand Down Expand Up @@ -254,13 +282,15 @@ public InputStream getTextToSpeech(String apiKey, String text, String locale, St
*
* It is in package scope to be accessed by tests.
*/
private String createURL(String apiKey, String text, String locale, String voice, String audioFormat) {
private String createURL(String apiKey, String text, String locale, String voice, String audioCodec,
String audioFormat) {
String encodedMsg = URLEncoder.encode(text, StandardCharsets.UTF_8);
String url = "http://api.voicerss.org/?key=" + apiKey + "&hl=" + locale + "&c=" + audioFormat;
String url = "http://api.voicerss.org/?key=" + apiKey + "&hl=" + locale + "&c=" + audioCodec + "&f="
+ audioFormat;
if (!DEFAULT_VOICE.equals(voice)) {
url += "&v=" + voice;
}
url += "&f=44khz_16bit_mono&src=" + encodedMsg;
url += "&src=" + encodedMsg;
return url;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ private void generateCacheForMessage(String apiKey, String cacheDir, String loca
return;
}
CachedVoiceRSSCloudImpl impl = new CachedVoiceRSSCloudImpl(cacheDir);
File cachedFile = impl.getTextToSpeechAsFile(apiKey, trimmedMsg, locale, voice, "MP3");
File cachedFile = impl.getTextToSpeechAsFile(apiKey, trimmedMsg, locale, voice, "MP3", null);
Copy link
Contributor

@lolodomo lolodomo Jan 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a little lost. Why is MP3 hardcoded here ?
And using null as last new parameter can not lead to problems (NPE) ?

More generally, using VoiceRSS with your changes, what will be the default codec used ? Will it be MP3 if the sink supports MP3 ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a little lost. Why is MP3 hardcoded here ?

"MP3" was already hardcoded before and not parameterized in the commit to "add support for OGG and AAC audio formats" because it's probably unused. So I also didn't touch it.

This file is meant to be called from the CLI, see Caching in README.md. Perhaps it should be deleted as calling the openhab:voice say <text> command in the runtime console has the same effect.

And using null as last new parameter can not lead to problems (NPE) ?

No risk of a NPE: if (!Objects.equals(format, "44khz_16bit_mono")) { ... }

More generally, using VoiceRSS with your changes, what will be the default codec used ? Will it be MP3 if the sink supports MP3?

Hm. Good question. I think so, if the sink does not support WAV. That's decided in VoiceManagerImpl:

Set<AudioFormat> audioFormats = tts.getSupportedFormats();
...
AudioFormat audioFormat = getBestMatch(audioFormats, sink.getSupportedFormats());

So I think that's not really an implementation concern of the VoiceRSS binding but the openhab-core classes.

Copy link
Contributor

@lolodomo lolodomo Jan 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that it was previously hardcoded with MP3 but I did not remember why. As I was probably the one who added OGG/AAC, if I left MP3 hardcoded here, there is certainly a good reason, I hope :) But I will check that again, considering your comments about the CLI command to build the cache.

I would like to be sure that these changes will not lead to users "loosing" their cache in practice because the binding is now using another codec and other file names in the cache.
Currently, I see that my cached files have all the MP3 extension.

Let me the time to analyse more in details the code and to test your changes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made sure not to change the filename for previous uses of the default 44khz_16bit_mono format:

/**
 * Gets a unique filename [...] suffixed by the format if it is not the default of "44khz_16bit_mono".
 */
private String getUniqueFilenameForText(String text, String locale, String voice, String format) {
    ...
    if (!Objects.equals(format, "44khz_16bit_mono")) {
        filename += "_" + format;
    }
    ...
}

Thanks for your review so far.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of audio sinks support MP3 and WAV: javasound, webaudio, chromecast, kodi, squeezebox, ...

VoiceManagerImpl#getPreferredFormat seems to prefer WAV, so I guess you're right that this change might lead to cache misses.

But it would be somewhat unfitting to have special "prefer MP3" code only in this binding. Better to implement something in openhab-core.

There's code duplication between VoiceManagerImpl#getPreferredFormat and AudioFormat#getPreferredFormat, the latter has a strange defaultFrequency = 16384 setting, that is not contained in the list of common sampling frequencies in Wikipedia (the correct value would be 16000 Hz). This is corrected in VoiceManagerImpl to 44100 Hz. I already wrote that bitRate is practically unused. bigEndian only makes sense for WAV with a bit depth of 16 bit but not for 8 bit WAV or MP3 or other compressed formats. Maybe it's time for a little cleanup and refactoring there.

Copy link
Contributor

@lolodomo lolodomo Jan 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should just add an advanced parameter to let the user force an audio format.
By default, the binding will support WAV, MP3 , OGG, ... If set to MP3 for example, the binding will only consider MP3 format.
Like that, we have a way to keep backward compatibility + compatibility with the cache tool.
WDYT?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A config option to opt-in to WAV support could be implemented. The return value of getSupportedFormats() could be without WAV in the default case then. But I think this is not something a single binding should do as a special case.

The code that decides what audio format to use is in openhab-core. I can't say if a sink can prefer a format by returning it first in the of list of supported formats. Oh, I see that it's a Set and not a list, so I guess returning a LinkedHashSet would be possible but isn't really part of the contract of AudioSink.

Another possibility is to mention how to batch convert the cache in the release notes:

for i in userdata/voicerss/cache/*.mp3; do sox "$i" "${i%.mp3}.wav"; done

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default, the binding will support WAV, MP3 , OGG, ... If set to MP3 for example, the binding will only consider MP3 format.
Like that, we have a way to keep backward compatibility

Listing the preferred audio formats to use as a config option in openhab-core would be a good idea. But would not solve our problem here, because -core preferred wav and should continue to do so when such an option is introduced, I guess.

So if you do not think a remark in the release notes is enough we should start to implement an option for the voicerss binding and maybe later on do something about it in -core. Perhaps even changing AudioSink to return a List. A sink should have the possibility to specify a preference.

Copy link
Contributor

@lolodomo lolodomo Jan 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I was thinking of is clearly an option in voicerss. My concern is users who created cached files for voicerss using the provided tool. Currently all these files are MP3 files.

My idea was a binding parameter that would limit the audio formats handled by the binding to a particular audio format, MP3 as an example.

But now I think the best option is just to enhance the tool to let the user select the output audio format for cached files to be created. Like that, to switch to WAV, files, the user will have only to rerurn his command but asking WAV format.

Can you enhance the tool with one additional optional command line argument that will be the audio codec to be used? The call will then use this argument rather than hardcoded string "MP3".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I will do that!

System.out.println(
"Created cached audio for locale='" + locale + "', msg='" + trimmedMsg + "' to file=" + cachedFile);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2022 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.voicerss.internal;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.openhab.core.audio.AudioFormat;

/**
* Hamcrest {@link Matcher} to assert a compatible {@link AudioFormat}.
*
* @author Andreas Brenk - Initial contribution
*/
public class CompatibleAudioFormatMatcher extends TypeSafeMatcher<AudioFormat> {

private final AudioFormat audioFormat;

public CompatibleAudioFormatMatcher(AudioFormat audioFormat) {
this.audioFormat = audioFormat;
}

@Override
protected boolean matchesSafely(AudioFormat actual) {
return audioFormat.isCompatible(actual);
}

@Override
public void describeTo(Description description) {
description.appendText("an audio format compatible to ").appendValue(audioFormat);
}

/**
* Creates a matcher that matches when the examined object is
* compatible to the specified <code>audioFormat</code>.
*
* @param audioFormat the audio format which must be compatible
*/
public static Matcher<AudioFormat> compatibleAudioFormat(AudioFormat audioFormat) {
return new CompatibleAudioFormatMatcher(audioFormat);
}
}
Loading