Java tutorial
/** * Copyright 2010 DFKI GmbH. * All Rights Reserved. Use is subject to license terms. * * This file is part of MARY TTS. * * MARY TTS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ package marytts.tools.voiceimport; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Reader; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.SortedMap; import java.util.TreeMap; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.apache.commons.lang.ArrayUtils; import com.twmacinta.util.MD5; import marytts.util.MaryUtils; /** * Package all of the data files required for the voice in a zip file and generate a component file with the corresponding * metadata. Also generate the voice config file and include it in the package. This package is put in MARYBASE/download. * <p> * <b>This component does <i>not</i> install the voice!</b> The Mary Component Installer must be run for that purpose. * * @author steiner (based on {@link VoiceInstaller}) * */ public class VoicePackager extends VoiceImportComponent { protected String name; protected DatabaseLayout db; protected String VOICETYPE; protected String EXAMPLETEXTFILE; protected String LICENSEURL; protected String VOICEDESCRIPTION; protected String VOCALIZATIONSUPPORT; // constants to access filenames in database component properties and organize file list: protected final String CARTFILE = "CARTBuilder.cartFile"; protected final String DURTREE = "DurationCARTTrainer.durTree"; protected final String F0LEFTTREE = "F0CARTTrainer.f0LeftTreeFile"; protected final String F0MIDTREE = "F0CARTTrainer.f0MidTreeFile"; protected final String F0RIGHTTREE = "F0CARTTrainer.f0RightTreeFile"; protected final String HALFPHONEFEATSAC = "AcousticFeatureFileWriter.acFeatureFile"; protected final String HALFPHONEFEATDEFAC = "AcousticFeatureFileWriter.acFeatDef"; protected final String HALFPHONEUNITS = "HalfPhoneUnitfileWriter.unitFile"; protected final String JOINCOSTFEATS = "JoinCostFileMaker.joinCostFile"; protected final String JOINCOSTFEATDEF = "JoinCostFileMaker.weightsFile"; protected final String PHONEFEATDEF = "PhoneFeatureFileWriter.weightsFile"; protected String TIMELINE = null; protected final String HNMTIMELINE = "HnmTimelineMaker.hnmTimeline"; protected final String WAVETIMELINE = "WaveTimelineMaker.waveTimeline"; protected final String BASETIMELINE = "BasenameTimelineMaker.timelineFile"; public VoicePackager() { this("VoicePackager"); } protected VoicePackager(String name) { super(); this.name = name; VOICETYPE = name + ".voiceType"; EXAMPLETEXTFILE = name + ".exampleTextFile"; LICENSEURL = name + ".licenseUrl"; VOICEDESCRIPTION = name + ".voiceDescription"; VOCALIZATIONSUPPORT = name + ".vocalizationSupport"; } /** * {@inheritDoc} */ @Override protected void setupHelp() { props2Help = new TreeMap<String, String>(); props2Help.put(VOICETYPE, "voice type; one of <b>unit selection</b>, <b>HSMM</b>, <b>FDPSOLA</b>, <b>HNM</b>" + " (note that support for FDPSOLA and HNM are experimental!)"); props2Help.put(EXAMPLETEXTFILE, "file containing example text (for limited domain voices only), leave blank for default"); props2Help.put(LICENSEURL, "URL of the license agreement for this voice" + " (<a href=\"http://creativecommons.org/licenses/by-nd/3.0/\">cc-by-nd</a> by default)"); props2Help.put(VOICEDESCRIPTION, "short text describing this voice"); props2Help.put(VOCALIZATIONSUPPORT, "if true package vocalization files with voice and set corresponding configuration settings"); } /** * {@inheritDoc} */ @Override public SortedMap<String, String> getDefaultProps(DatabaseLayout databaseLayout) { this.db = databaseLayout; if (props == null) { props = new TreeMap<String, String>(); String voiceType = System.getProperty("VOICETYPE", "unit selection"); props.put(VOICETYPE, voiceType); String exampleText = System.getProperty("EXAMPLETEXT", ""); props.put(EXAMPLETEXTFILE, exampleText); String licenseUrl = System.getProperty("LICENSEURL", "http://mary.dfki.de/download/by-nd-3.0.html"); props.put(LICENSEURL, licenseUrl); String voiceDescription = System.getProperty("VOICEDESCRIPTION", ""); props.put(VOICEDESCRIPTION, voiceDescription); props.put(VOCALIZATIONSUPPORT, "false"); } return props; } /** * {@inheritDoc} */ @Override public String getName() { return name; } /** * {@inheritDoc} * * @throws Exception */ @Override public boolean compute() throws Exception { // (0) ensure that properties have valid values: validateProperties(); // (1) gather files required by this voice in a convenient structure: HashMap<String, File> files = getVoiceDataFiles(); // (2) create config file and add it to the files: try { File configFile = createVoiceConfig(files); files.put("CONFIG", configFile); } catch (FileNotFoundException e) { e.printStackTrace(); return false; } // (3) create zip file and component file (directly at their destination, MARYBASE/download): try { File zipFile = createZipFile(files); createComponentFile(zipFile); } catch (Exception e) { e.printStackTrace(); return false; } logger.info("The voice package and component file have been successfully created and placed in " + getMaryBase() + "download"); logger.info("IMPORTANT: You must run the MARY Component Installer to install the voice!"); return true; } /** * Check various properties for invalid values * * @throws Exception */ protected void validateProperties() throws Exception { // ensure that voice type is supported: if (!getProp(VOICETYPE).toLowerCase().matches("(unit selection|fdpsola|hnm|hsmm)")) { throw new Exception("Unsupported voice type: " + getProp(VOICETYPE)); } // check for valid license URL: try { new URL(getProp(LICENSEURL)); } catch (MalformedURLException e) { throw new MalformedURLException(getProp(LICENSEURL) + " is not a valid URL!"); } } /** * Get the data files for this voice (which go into <tt>MARYBASE/lib/voices</tt>) and put them in a <property, File> Map * so that they can be accessed individually by the corresponding property key, e.g. "WaveTimelineMaker.waveTimeline" → * File("VOICE_DIR/mary/timeline_waves.mry") * <p> * TODO The way individual components provide access to their properties is a little patchy, so for now, this method is a * little fragile! * * @return the file Map */ protected HashMap<String, File> getVoiceDataFiles() { HashMap<String, File> files = new HashMap<String, File>(); if (getProp(VOICETYPE).equalsIgnoreCase("HNM")) { TIMELINE = HNMTIMELINE; } else { TIMELINE = WAVETIMELINE; } String[] properties = { CARTFILE, DURTREE, F0LEFTTREE, F0MIDTREE, F0RIGHTTREE, HALFPHONEFEATSAC, HALFPHONEFEATDEFAC, HALFPHONEUNITS, JOINCOSTFEATS, JOINCOSTFEATDEF, PHONEFEATDEF, TIMELINE, BASETIMELINE, EXAMPLETEXTFILE }; // vocalization files, if available: if ("true".equals(getProp(VOCALIZATIONSUPPORT))) { String[] vocalizationProperties = { "VocalizationFeatureFileWriter.featureDefinition", "VocalizationTimelineMaker.waveTimeline", "VocalizationFeatureFileWriter.featureFile", "VocalizationUnitfileWriter.unitFile", "VocalizationIntonationWriter.intonationTimeLineFile", "HNMFeatureFileWriter.hnmAnalysisTimelineFile", "VocalizationIntonationWriter.intonationFeatureDefinition" }; properties = (String[]) ArrayUtils.addAll(properties, vocalizationProperties); } for (String property : properties) { String fileName; try { fileName = db.getProperty(property); } catch (NullPointerException e) { throw e; } File file = new File(fileName); files.put(property, file); } return files; } /** * Create config file for this voice. * * @param files * <property, File> Map, e.g. "WaveTimelineMaker.waveTimeline" → * File("VOICE_DIR/mary/timeline_waves.mry") * @return the config File object * @throws FileNotFoundException */ protected File createVoiceConfig(HashMap<String, File> files) throws FileNotFoundException { // open the config file for writing: String configFileName = String.format("%s-%s.config", getVoiceLocale(), getVoiceName()); logger.info("Creating voice configuration file " + configFileName); File configFile = new File(getVoiceFileDir() + configFileName); PrintWriter out = new PrintWriter(configFile); // generate the config file contents, line by line: out.format("# Auto-generated config file for voice %s\r\n\r\n", getVoiceName()); out.format("name = %s\r\n", getVoiceName()); out.format("# Declare \"group names\" as component that other components can require.\r\n"); out.format("# These correspond to abstract \"groups\" of which this component is an instance.\r\n"); out.format("provides = \\\r\n\t%s-voice\r\n\r\n", getVoiceLocale()); // TODO these seem to be ignored by MaryProperties, are they really needed? out.format("%s-voice.version = %s\r\n\r\n", getVoiceLocale(), getMaryVersion()); out.format("voice.version = %s\r\n\r\n", getMaryVersion()); out.format("# List the dependencies, as a whitespace-separated list.\r\n"); out.format("# For each required component, an optional minimum version and an optional\r\n"); out.format("# download url can be given.\r\n"); out.format("# We can require a component by name or by an abstract \"group name\"\r\n"); out.format("# as listed under the \"provides\" element.\r\n"); out.format("requires = \\\r\n\t%s \\\r\n\tmarybase\r\n\r\n", getVoiceLocale()); out.format("requires.marybase.version = %s\r\n", getMaryVersion()); out.format("requires.%s.version = %s\r\n", getVoiceLocale(), getMaryVersion()); // TODO: this is obviously a placeholder url; do we really need this? out.format("requires.%s.download = http://mary.dfki.de/download/mary-install-4.x.x.jar\r\n", getVoiceLocale()); out.format("####################################################################\r\n"); out.format("####################### Module settings ###########################\r\n"); out.format("####################################################################\r\n"); out.format("# For keys ending in \".list\", values will be appended across config files,\r\n"); out.format("# so that .list keys can occur in several config files.\r\n"); out.format("# For all other keys, values will be copied to the global config, so\r\n"); out.format("# keys should be unique across config files.\r\n"); out.format("# If this setting is not present, a default value of 0 is assumed.\r\n"); out.format("voice.%s.wants.to.be.default = 20\r\n\r\n", getVoiceName()); out.format("# Add your voice to the list of Unit Selection Voices\r\n"); out.format("unitselection.voices.list = \\\r\n\t%s\r\n\r\n", getVoiceName()); out.format("# Set your voice specifications\r\n"); out.format("voice.%s.gender = %s\r\n", getVoiceName(), getVoiceGender()); out.format("voice.%s.locale = %s\r\n", getVoiceName(), getVoiceLocale()); out.format("voice.%s.domain = %s\r\n", getVoiceName(), getVoiceDomain()); out.format("voice.%s.samplingRate = %d\r\n\r\n", getVoiceName(), getVoiceSamplingRate()); out.format("# Relative weight of the target cost function vs. the join cost function\r\n"); out.format("voice.%s.viterbi.wTargetCosts = 0.7\r\n\r\n", getVoiceName()); out.format("# Beam size in dynamic programming: smaller => faster but worse quality.\r\n"); out.format("# (set to -1 to disable beam search; very slow but best available quality)\r\n"); out.format("voice.%s.viterbi.beamsize = 100\r\n\r\n", getVoiceName()); // TODO surely this should be dependent on having locale == "de"? out.format("# Sampa mapping for German voices \r\n"); out.format("voice.%s.sampamap = \\\r\n", getVoiceName()); out.format("\t=6->6 \\\r\n"); out.format("\t=n->n \\\r\n"); out.format("\t=m->m \\\r\n"); out.format("\t=N->N \\\r\n"); out.format("\t=l->l \\\r\n"); out.format("\ti->i: \\\r\n"); out.format("\te->e: \\\r\n"); out.format("\tu->u: \\\r\n"); out.format("\to->o: \r\n\r\n"); out.format("# Java classes to use for the various unit selection components\r\n"); out.format("voice.%s.databaseClass = marytts.unitselection.data.DiphoneUnitDatabase\r\n", getVoiceName()); out.format("voice.%s.selectorClass = marytts.unitselection.select.DiphoneUnitSelector\r\n", getVoiceName()); if (getProp(VOICETYPE).equalsIgnoreCase("HNM")) { out.format("voice.%s.concatenatorClass = marytts.unitselection.concat.HnmUnitConcatenator\r\n", getVoiceName()); } else if (getProp(VOICETYPE).equalsIgnoreCase("FDPSOLA")) { out.format( "voice.%s.concatenatorClass = marytts.unitselection.concat.FdpsolaUnitConcatenator\r\n", getVoiceName()); } else { out.format( "voice.%s.concatenatorClass = marytts.unitselection.concat.OverlapUnitConcatenator\r\n", getVoiceName()); } out.format( "voice.%s.targetCostClass = marytts.unitselection.select.DiphoneFFRTargetCostFunction\r\n", getVoiceName()); out.format("voice.%s.joinCostClass = marytts.unitselection.select.JoinCostFeatures\r\n", getVoiceName()); out.format("voice.%s.unitReaderClass = marytts.unitselection.data.UnitFileReader\r\n", getVoiceName()); out.format("voice.%s.cartReaderClass = marytts.cart.io.MARYCartReader\r\n", getVoiceName()); if (getProp(VOICETYPE).equalsIgnoreCase("HNM")) { out.format("voice.%s.audioTimelineReaderClass = marytts.unitselection.data.HnmTimelineReader\r\n\r\n", getVoiceName()); } else { out.format("voice.%s.audioTimelineReaderClass = marytts.unitselection.data.TimelineReader\r\n\r\n", getVoiceName()); } out.format("# Voice-specific files\r\n"); out.format("voice.%s.featureFile = MARY_BASE/lib/voices/%s/%s\r\n", getVoiceName(), getVoiceName(), files.get(HALFPHONEFEATSAC).getName()); out.format("voice.%s.targetCostWeights = MARY_BASE/lib/voices/%s/%s\r\n", getVoiceName(), getVoiceName(), files.get(HALFPHONEFEATDEFAC).getName()); out.format("voice.%s.joinCostFile = MARY_BASE/lib/voices/%s/%s\r\n", getVoiceName(), getVoiceName(), files.get(JOINCOSTFEATS).getName()); out.format("voice.%s.joinCostWeights = MARY_BASE/lib/voices/%s/%s\r\n", getVoiceName(), getVoiceName(), files.get(JOINCOSTFEATDEF).getName()); out.format("voice.%s.unitsFile = MARY_BASE/lib/voices/%s/%s\r\n", getVoiceName(), getVoiceName(), files.get(HALFPHONEUNITS).getName()); out.format("voice.%s.cartFile = MARY_BASE/lib/voices/%s/%s\r\n", getVoiceName(), getVoiceName(), files.get(CARTFILE).getName()); out.format("voice.%s.audioTimelineFile = MARY_BASE/lib/voices/%s/%s\r\n", getVoiceName(), getVoiceName(), files.get(TIMELINE).getName()); out.format("voice.%s.basenameTimeline = MARY_BASE/lib/voices/%s/%s\r\n\r\n", getVoiceName(), getVoiceName(), files.get(BASETIMELINE).getName()); if (getVoiceDomain().equalsIgnoreCase("limited") || getProp(EXAMPLETEXTFILE).length() != 0) { out.format("# Location of example text\r\n"); out.format("voice.%s.exampleTextFile = MARY_BASE/lib/voices/%s/%s\r\n\r\n", getVoiceName(), getVoiceName(), files.get(EXAMPLETEXTFILE).getName()); } out.format("# Modules to use for predicting acoustic target features for this voice:\r\n\r\n"); out.format("voice.%s.acousticModels = duration F0 midF0 rightF0\r\n\r\n", getVoiceName()); out.format("voice.%s.duration.model = cart\r\n", getVoiceName()); out.format("voice.%s.duration.data = MARY_BASE/lib/voices/%s/%s\r\n", getVoiceName(), getVoiceName(), files.get(DURTREE).getName()); out.format("voice.%s.duration.attribute = d\r\n\r\n", getVoiceName()); out.format("voice.%s.F0.model = cart\r\n", getVoiceName()); out.format("voice.%s.F0.data = MARY_BASE/lib/voices/%s/%s\r\n", getVoiceName(), getVoiceName(), files.get(F0LEFTTREE).getName()); out.format("voice.%s.F0.attribute = f0\r\n", getVoiceName()); out.format("voice.%s.F0.attribute.format = (0,%%.0f)\r\n", getVoiceName()); out.format("voice.%s.F0.predictFrom = firstVowels\r\n", getVoiceName()); out.format("voice.%s.F0.applyTo = firstVoicedSegments\r\n\r\n", getVoiceName()); out.format("voice.%s.midF0.model = cart\r\n", getVoiceName()); out.format("voice.%s.midF0.data = MARY_BASE/lib/voices/%s/%s\r\n", getVoiceName(), getVoiceName(), files.get(F0MIDTREE).getName()); out.format("voice.%s.midF0.attribute = f0\r\n", getVoiceName()); out.format("voice.%s.midF0.attribute.format = (50,%%.0f)\r\n", getVoiceName()); out.format("voice.%s.midF0.predictFrom = firstVowels\r\n", getVoiceName()); out.format("voice.%s.midF0.applyTo = firstVowels\r\n\r\n", getVoiceName()); out.format("voice.%s.rightF0.model = cart\r\n", getVoiceName()); out.format("voice.%s.rightF0.data = MARY_BASE/lib/voices/%s/%s\r\n", getVoiceName(), getVoiceName(), files.get(F0RIGHTTREE).getName()); out.format("voice.%s.rightF0.attribute = f0\r\n", getVoiceName()); out.format("voice.%s.rightF0.attribute.format = (100,%%.0f)\r\n", getVoiceName()); out.format("voice.%s.rightF0.predictFrom = firstVowels\r\n", getVoiceName()); out.format("voice.%s.rightF0.applyTo = lastVoicedSegments\r\n", getVoiceName()); // vocalization support, if available: if ("true".equals(getProp(VOCALIZATIONSUPPORT))) { out.format("\r\n# support for synthesis of vocalizations\r\n"); out.format("voice.%s.vocalizationSupport = true\r\n", getVoiceName()); out.format("voice.%s.vocalization.unitfile = MARY_BASE/lib/voices/%s/vocalization_units.mry\r\n", getVoiceName(), getVoiceName()); out.format( "voice.%s.vocalization.timeline = MARY_BASE/lib/voices/%s/vocalization_wave_timeline.mry\r\n", getVoiceName(), getVoiceName()); out.format("voice.%s.vocalization.featurefile = MARY_BASE/lib/voices/%s/vocalization_features.mry\r\n", getVoiceName(), getVoiceName()); out.format( "voice.%s.vocalization.featureDefinitionFile = MARY_BASE/lib/voices/%s/vocalization_feature_definition.txt\r\n", getVoiceName(), getVoiceName()); out.format( "voice.%s.vocalization.intonationfile = MARY_BASE/lib/voices/%s/vocalization_intonation.mry\r\n", getVoiceName(), getVoiceName()); out.format("voice.%s.vocalization.synthesisTechnology = fdpsola\r\n\r\n", getVoiceName()); out.format("voice.%s.f0ContourImposeSupport = true\r\n", getVoiceName()); out.format("voice.%s.vocalization.usePrecondition = true\r\n", getVoiceName()); out.format("voice.%s.vocalization.contourCostWeight = 0.05\r\n", getVoiceName()); out.format( "voice.%s.vocalization.intonation.featureDefinitionFile = MARY_BASE/lib/voices/%s/vocalization_f0_feature_definition.txt\r\n", getVoiceName(), getVoiceName()); out.format("voice.%s.vocalization.intonation.numberOfSuitableUnits = 5\r\n", getVoiceName()); } out.close(); return configFile; } /** * Create zip file containing all of the voice files (including the config file, which should be in <b>files</b>). * * @param files * <property, File> Map, e.g. "WaveTimelineMaker.waveTimeline" → * File("VOICE_DIR/mary/timeline_waves.mry") * @return the zip File object * @throws Exception */ protected File createZipFile(HashMap<String, File> files) throws Exception { // TODO this should probably be optimized by using buffered Readers and Writer: byte[] buffer = new byte[4096]; // initialize zip file: String zipFileName = String.format("mary-%s-%s.zip", getVoiceName(), getMaryVersion()); logger.info("Creating voice package " + zipFileName); File zipFile = new File(getMaryBase() + "download" + File.separator + zipFileName); FileOutputStream outputStream = new FileOutputStream(zipFile); ZipOutputStream zipStream = new ZipOutputStream(new FileOutputStream(zipFile)); // TODO this doesn't explicitly create each ancestor of the voicePath as a separate directory entry in the zip file, but // that doesn't seem necessary: String voicePath = "lib" + File.separator + "voices" + File.separator + getVoiceName() + File.separator; // iterate over files: for (String key : files.keySet()) { File file = files.get(key); // open data file for reading: FileInputStream inputStream = null; try { inputStream = new FileInputStream(file); } catch (FileNotFoundException e) { if (key.equals(EXAMPLETEXTFILE) && getProp(EXAMPLETEXTFILE).length() == 0 && !getVoiceDomain().equalsIgnoreCase("limited")) { logger.debug("Example text file " + getProp(EXAMPLETEXTFILE) + " not found, ignoring."); continue; } else { logger.error("File " + file + " not found!"); throw e; } } // make new entry in zip file, with the appropriate target path: logger.debug("Deflating file " + file); if (key.equals("CONFIG")) { zipStream.putNextEntry(new ZipEntry("conf" + File.separator + file.getName())); } else { zipStream.putNextEntry(new ZipEntry(voicePath + file.getName())); } int len; // stream file contents into zip file: while ((len = inputStream.read(buffer)) > 0) { zipStream.write(buffer, 0, len); } // complete entry and close data file: zipStream.closeEntry(); inputStream.close(); } // close zip file: zipStream.close(); return zipFile; } /** * Create component file for this voice. This includes various metadata, including the zip file name and MD5 hash, and several * other attributes. * * @param zipFile * @throws Exception */ protected void createComponentFile(File zipFile) throws Exception { logger.info("Hashing voice package"); String zipFileMd5Hash = MD5.asHex(MD5.getHash(zipFile)); String componentFileName = String.format("mary-%s-%s-component.xml", getVoiceName(), getMaryVersion()); logger.info("Creating component file " + componentFileName); File componentFile = new File( getMaryBase() + File.separator + "download" + File.separator + componentFileName); PrintWriter out = new PrintWriter(componentFile); // avoid overhead of XML handling by generating the XML with raw strings: out.format("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); out.format("<marytts-install xmlns=\"http://mary.dfki.de/installer\">\n"); out.format(" <voice gender=\"%s\" locale=\"%s\" name=\"%s\" type=\"%s\" version=\"%s\">\n", getVoiceGender(), getXMLCompatibleVoiceLocale(), getVoiceName(), getProp(VOICETYPE), getMaryVersion()); out.format(" <description>%s</description>\n", getProp(VOICEDESCRIPTION)); out.format(" <license href=\"%s\"/>\n", getProp(LICENSEURL)); out.format(" <package filename=\"%s\"\n", zipFile.getName()); out.format(" md5sum=\"%s\" size=\"%d\">\n", zipFileMd5Hash, zipFile.length()); out.format(" <location href=\"http://mary.dfki.de/download/%s/\"/>\n", getMaryVersion()); out.format(" </package>\n"); out.format(" <depends language=\"%s\" version=\"%s\"/>\n", getXMLCompatibleVoiceLocale(), getMaryVersion()); out.format(" </voice>\n"); out.format("</marytts-install>\n"); out.close(); } /** * {@inheritDoc} */ @Override public int getProgress() { // TODO Auto-generated method stub return -1; } // several getters: // TODO these really belong in DatabaseLayout! public String getVoiceLocale() { return MaryUtils.string2locale(db.getProp(db.LOCALE)).toString(); } public String getXMLCompatibleVoiceLocale() { return MaryUtils.locale2xmllang(MaryUtils.string2locale(db.getProp(db.LOCALE))); } public String getVoiceName() { return db.getProp(db.VOICENAME).toLowerCase(); } public String getVoiceFileDir() { return db.getProp(db.FILEDIR); } public String getMaryVersion() { // TODO temporary workaround for ticket:360 String compatibleVersion = db.getProp(db.MARYBASEVERSION); if (compatibleVersion.equals("trunk")) { compatibleVersion = "4.0.0"; } return compatibleVersion; } public String getVoiceGender() { return db.getProp(db.GENDER).toLowerCase(); } public String getVoiceDomain() { return db.getProp(db.DOMAIN).toLowerCase(); } public int getVoiceSamplingRate() { return Integer.parseInt(db.getProp(db.SAMPLINGRATE)); } public String getMaryBase() { return db.getProp(db.MARYBASE) + File.separator; } }