import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
-import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Stream;
/**
* This class implements the Cloud service from VoiceRSS. For more information,
- * see API documentation at http://www.voicerss.org/api/documentation.aspx.
+ * see API documentation at http://www.voicerss.org/api .
*
* Current state of implementation:
* <ul>
*/
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());
SUPPORTED_LOCALES.add(Locale.forLanguageTag("cs-cz"));
SUPPORTED_LOCALES.add(Locale.forLanguageTag("da-dk"));
SUPPORTED_LOCALES.add(Locale.forLanguageTag("de-at"));
- SUPPORTED_LOCALES.add(Locale.forLanguageTag("de-ch"));
SUPPORTED_LOCALES.add(Locale.forLanguageTag("de-de"));
+ SUPPORTED_LOCALES.add(Locale.forLanguageTag("de-ch"));
SUPPORTED_LOCALES.add(Locale.forLanguageTag("el-gr"));
SUPPORTED_LOCALES.add(Locale.forLanguageTag("en-au"));
SUPPORTED_LOCALES.add(Locale.forLanguageTag("en-ca"));
SUPPORTED_LOCALES.add(Locale.forLanguageTag("es-mx"));
SUPPORTED_LOCALES.add(Locale.forLanguageTag("fi-fi"));
SUPPORTED_LOCALES.add(Locale.forLanguageTag("fr-ca"));
- SUPPORTED_LOCALES.add(Locale.forLanguageTag("fr-ch"));
SUPPORTED_LOCALES.add(Locale.forLanguageTag("fr-fr"));
+ SUPPORTED_LOCALES.add(Locale.forLanguageTag("fr-ch"));
SUPPORTED_LOCALES.add(Locale.forLanguageTag("he-il"));
SUPPORTED_LOCALES.add(Locale.forLanguageTag("hi-in"));
SUPPORTED_LOCALES.add(Locale.forLanguageTag("hr-hr"));
SUPPORTED_LOCALES.add(Locale.forLanguageTag("zh-tw"));
}
- private static final Set<String> SUPPORTED_VOICES = Collections.singleton("VoiceRSS");
+ private static final Map<String, Set<String>> SUPPORTED_VOICES = new HashMap<>();
+ static {
+ SUPPORTED_VOICES.put("ar-eg", Set.of("Oda"));
+ SUPPORTED_VOICES.put("ar-sa", Set.of("Salim"));
+ SUPPORTED_VOICES.put("bg-bg", Set.of("Dimo"));
+ SUPPORTED_VOICES.put("ca-es", Set.of("Rut"));
+ SUPPORTED_VOICES.put("cs-cz", Set.of("Josef"));
+ SUPPORTED_VOICES.put("da-dk", Set.of("Freja"));
+ SUPPORTED_VOICES.put("de-at", Set.of("Lukas"));
+ SUPPORTED_VOICES.put("de-de", Set.of("Hanna", "Lina", "Jonas"));
+ SUPPORTED_VOICES.put("de-ch", Set.of("Tim"));
+ SUPPORTED_VOICES.put("el-gr", Set.of("Neo"));
+ SUPPORTED_VOICES.put("en-au", Set.of("Zoe", "Isla", "Evie", "Jack"));
+ SUPPORTED_VOICES.put("en-ca", Set.of("Rose", "Clara", "Emma", "Mason"));
+ SUPPORTED_VOICES.put("en-gb", Set.of("Alice", "Nancy", "Lily", "Harry"));
+ SUPPORTED_VOICES.put("en-ie", Set.of("Oran"));
+ SUPPORTED_VOICES.put("en-in", Set.of("Eka", "Jai", "Ajit"));
+ SUPPORTED_VOICES.put("en-us", Set.of("Linda", "Amy", "Mary", "John", "Mike"));
+ SUPPORTED_VOICES.put("es-es", Set.of("Camila", "Sofia", "Luna", "Diego"));
+ SUPPORTED_VOICES.put("es-mx", Set.of("Juana", "Silvia", "Teresa", "Jose"));
+ SUPPORTED_VOICES.put("fi-fi", Set.of("Aada"));
+ SUPPORTED_VOICES.put("fr-ca", Set.of("Emile", "Olivia", "Logan", "Felix"));
+ SUPPORTED_VOICES.put("fr-fr", Set.of("Bette", "Iva", "Zola", "Axel"));
+ SUPPORTED_VOICES.put("fr-ch", Set.of("Theo"));
+ SUPPORTED_VOICES.put("he-il", Set.of("Rami"));
+ SUPPORTED_VOICES.put("hi-in", Set.of("Puja", "Kabir"));
+ SUPPORTED_VOICES.put("hr-hr", Set.of("Nikola"));
+ SUPPORTED_VOICES.put("hu-hu", Set.of("Mate"));
+ SUPPORTED_VOICES.put("id-id", Set.of("Intan"));
+ SUPPORTED_VOICES.put("it-it", Set.of("Bria", "Mia", "Pietro"));
+ SUPPORTED_VOICES.put("ja-jp", Set.of("Hina", "Airi", "Fumi", "Akira"));
+ SUPPORTED_VOICES.put("ko-kr", Set.of("Nari"));
+ SUPPORTED_VOICES.put("ms-my", Set.of("Aqil"));
+ SUPPORTED_VOICES.put("nb-no", Set.of("Marte", "Erik"));
+ SUPPORTED_VOICES.put("nl-be", Set.of("Daan"));
+ SUPPORTED_VOICES.put("nl-nl", Set.of("Lotte", "Bram"));
+ SUPPORTED_VOICES.put("pl-pl", Set.of("Julia", "Jan"));
+ SUPPORTED_VOICES.put("pt-br", Set.of("Marcia", "Ligia", "Yara", "Dinis"));
+ SUPPORTED_VOICES.put("pt-pt", Set.of("Leonor"));
+ SUPPORTED_VOICES.put("ro-ro", Set.of("Doru"));
+ SUPPORTED_VOICES.put("ru-ru", Set.of("Olga", "Marina", "Peter"));
+ SUPPORTED_VOICES.put("sk-sk", Set.of("Beda"));
+ SUPPORTED_VOICES.put("sl-si", Set.of("Vid"));
+ SUPPORTED_VOICES.put("sv-se", Set.of("Molly", "Hugo"));
+ SUPPORTED_VOICES.put("ta-in", Set.of("Sai"));
+ SUPPORTED_VOICES.put("th-th", Set.of("Ukrit"));
+ SUPPORTED_VOICES.put("tr-tr", Set.of("Omer"));
+ SUPPORTED_VOICES.put("vi-vn", Set.of("Chi"));
+ SUPPORTED_VOICES.put("zh-cn", Set.of("Luli", "Shu", "Chow", "Wang"));
+ SUPPORTED_VOICES.put("zh-hk", Set.of("Jia", "Xia", "Chen"));
+ SUPPORTED_VOICES.put("zh-tw", Set.of("Akemi", "Lin", "Lee"));
+ }
@Override
public Set<String> getAvailableAudioFormats() {
@Override
public Set<String> getAvailableVoices() {
- return SUPPORTED_VOICES;
+ // different locales support different voices, so let's list all here in one big set when no locale is provided
+ Set<String> allvoxes = new HashSet<>();
+ allvoxes.add(DEFAULT_VOICE);
+ for (Set<String> langvoxes : SUPPORTED_VOICES.values()) {
+ for (String langvox : langvoxes) {
+ allvoxes.add(langvox);
+ }
+ }
+ return allvoxes;
}
@Override
public Set<String> getAvailableVoices(Locale locale) {
- for (Locale voiceLocale : SUPPORTED_LOCALES) {
- if (voiceLocale.toLanguageTag().equalsIgnoreCase(locale.toLanguageTag())) {
- return SUPPORTED_VOICES;
+ Set<String> allvoxes = new HashSet<>();
+ allvoxes.add(DEFAULT_VOICE);
+ // all maps must be defined with key in lowercase
+ String langtag = locale.toLanguageTag().toLowerCase();
+ if (SUPPORTED_VOICES.containsKey(langtag)) {
+ for (String langvox : SUPPORTED_VOICES.get(langtag)) {
+ allvoxes.add(langvox);
}
}
- return new HashSet<>();
+ return allvoxes;
}
/**
* dependencies.
*/
@Override
- public InputStream getTextToSpeech(String apiKey, String text, String locale, String audioFormat)
+ public InputStream getTextToSpeech(String apiKey, String text, String locale, String voice, String audioFormat)
throws IOException {
- String url = createURL(apiKey, text, locale, audioFormat);
+ String url = createURL(apiKey, text, locale, voice, audioFormat);
logger.debug("Call {}", url);
URLConnection connection = new URL(url).openConnection();
*
* It is in package scope to be accessed by tests.
*/
- private String createURL(String apiKey, String text, String locale, String audioFormat) {
+ private String createURL(String apiKey, String text, String locale, String voice, String audioFormat) {
String encodedMsg;
try {
encodedMsg = URLEncoder.encode(text, "UTF-8");
// fall through and use msg un-encoded
encodedMsg = text;
}
- return "http://api.voicerss.org/?key=" + apiKey + "&hl=" + locale + "&c=" + audioFormat
- + "&f=44khz_16bit_mono&src=" + encodedMsg;
+ String url = "http://api.voicerss.org/?key=" + apiKey + "&hl=" + locale + "&c=" + audioFormat;
+ if (!DEFAULT_VOICE.equals(voice)) {
+ url += "&v=" + voice;
+ }
+ url += "&f=44khz_16bit_mono&src=" + encodedMsg;
+ return url;
}
}
String apiKey = args[1];
String cacheDir = args[2];
String locale = args[3];
- if (args[4].startsWith("@")) {
- String inputFileName = args[4].substring(1);
+ String voice = args[4];
+ if (args[5].startsWith("@")) {
+ String inputFileName = args[5].substring(1);
File inputFile = new File(inputFileName);
if (!inputFile.exists()) {
usage();
System.err.println("File " + inputFileName + " not found");
return RC_INPUT_FILE_NOT_FOUND;
}
- generateCacheForFile(apiKey, cacheDir, locale, inputFileName);
+ generateCacheForFile(apiKey, cacheDir, locale, voice, inputFileName);
} else {
- String text = args[4];
- generateCacheForMessage(apiKey, cacheDir, locale, text);
+ String text = args[5];
+ generateCacheForMessage(apiKey, cacheDir, locale, voice, text);
}
return RC_OK;
}
System.out.println(" key the VoiceRSS API Key, e.g. \"123456789\"");
System.out.println(" cache-dir is directory where the files will be stored, e.g. \"voicerss-cache\"");
System.out.println(" locale the language locale, has to be valid, e.g. \"en-us\", \"de-de\"");
+ System.out.println(" voice the voice, \"default\" for the default voice");
System.out.println(" text the text to create audio file for, e.g. \"Hello World\"");
System.out.println(
" inputfile a name of a file, where all lines will be translatet to text, e.g. \"@message.txt\"");
System.out.println();
}
- private void generateCacheForFile(String apiKey, String cacheDir, String locale, String inputFileName)
+ private void generateCacheForFile(String apiKey, String cacheDir, String locale, String voice, String inputFileName)
throws IOException {
File inputFile = new File(inputFileName);
try (BufferedReader br = new BufferedReader(new FileReader(inputFile))) {
String line;
while ((line = br.readLine()) != null) {
// process the line.
- generateCacheForMessage(apiKey, cacheDir, locale, line);
+ generateCacheForMessage(apiKey, cacheDir, locale, voice, line);
}
}
}
- private void generateCacheForMessage(String apiKey, String cacheDir, String locale, String msg) throws IOException {
+ private void generateCacheForMessage(String apiKey, String cacheDir, String locale, String voice, String msg)
+ throws IOException {
if (msg == null) {
System.err.println("Ignore msg=null");
return;
return;
}
CachedVoiceRSSCloudImpl impl = new CachedVoiceRSSCloudImpl(cacheDir);
- File cachedFile = impl.getTextToSpeechAsFile(apiKey, trimmedMsg, locale, "MP3");
+ File cachedFile = impl.getTextToSpeechAsFile(apiKey, trimmedMsg, locale, voice, "MP3");
System.out.println(
"Created cached audio for locale='" + locale + "', msg='" + trimmedMsg + "' to file=" + cachedFile);
}