Skip to content

Commit 9e03945

Browse files
authored
[marytts] Update dependencies (#21440)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
1 parent 1719ae5 commit 9e03945

3 files changed

Lines changed: 289 additions & 11 deletions

File tree

bundles/org.openhab.voice.marytts/pom.xml

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@
1515
<name>openHAB Add-ons :: Bundles :: Voice :: Mary Text-to-Speech</name>
1616

1717
<properties>
18-
<bnd.importpackage>com.twmacinta.util;resolution:=optional,gnu.trove;resolution:=optional,Jampack;resolution:=optional,net.didion.jwnl*;resolution:=optional,org.apache.http*;resolution:=optional,org.apache.xerces.impl*;resolution:=optional,org.hsqldb;resolution:=optional,org.jdesktop.layout*;resolution:=optional;sun.nio.ch.*</bnd.importpackage>
18+
<bnd.importpackage>com.twmacinta.util;resolution:=optional,gnu.trove;resolution:=optional,Jampack;resolution:=optional,com.sun.*;resolution:=optional,jdk.javadoc.doclet;resolution:=optional,org.apache.http*;resolution:=optional,org.hsqldb;resolution:=optional,org.jdesktop.layout*;resolution:=optional,org.apache.xerces.impl*;resolution:=optional,sun.nio.ch.*;resolution:=optional</bnd.importpackage>
19+
<dfkimarytts.version>5.2.1</dfkimarytts.version>
1920
</properties>
2021

2122
<dependencies>
2223
<dependency>
2324
<groupId>com.ibm.icu</groupId>
2425
<artifactId>icu4j</artifactId>
25-
<version>54.1.1</version>
26+
<version>78.3</version>
2627
<scope>compile</scope>
2728
</dependency>
2829
<dependency>
@@ -46,7 +47,7 @@
4647
<dependency>
4748
<groupId>de.dfki.lt.jtok</groupId>
4849
<artifactId>jtok-core</artifactId>
49-
<version>1.9.3</version>
50+
<version>1.9.4</version>
5051
<scope>compile</scope>
5152
<exclusions>
5253
<exclusion>
@@ -62,13 +63,13 @@
6263
<dependency>
6364
<groupId>de.dfki.mary</groupId>
6465
<artifactId>emotionml-checker-java</artifactId>
65-
<version>1.1</version>
66+
<version>1.2.3</version>
6667
<scope>compile</scope>
6768
</dependency>
6869
<dependency>
6970
<groupId>de.dfki.mary</groupId>
7071
<artifactId>marytts-common</artifactId>
71-
<version>5.2.1</version>
72+
<version>${dfkimarytts.version}</version>
7273
<scope>compile</scope>
7374
<exclusions>
7475
<exclusion>
@@ -84,25 +85,25 @@
8485
<dependency>
8586
<groupId>de.dfki.mary</groupId>
8687
<artifactId>marytts-lang-de</artifactId>
87-
<version>5.2.1</version>
88+
<version>${dfkimarytts.version}</version>
8889
<scope>compile</scope>
8990
</dependency>
9091
<dependency>
9192
<groupId>de.dfki.mary</groupId>
9293
<artifactId>marytts-lang-en</artifactId>
93-
<version>5.2.1</version>
94+
<version>${dfkimarytts.version}</version>
9495
<scope>compile</scope>
9596
</dependency>
9697
<dependency>
9798
<groupId>de.dfki.mary</groupId>
9899
<artifactId>marytts-runtime</artifactId>
99-
<version>5.2.1</version>
100+
<version>${dfkimarytts.version}</version>
100101
<scope>compile</scope>
101102
</dependency>
102103
<dependency>
103104
<groupId>de.dfki.mary</groupId>
104105
<artifactId>marytts-signalproc</artifactId>
105-
<version>5.2.1</version>
106+
<version>${dfkimarytts.version}</version>
106107
<scope>compile</scope>
107108
</dependency>
108109
<dependency>
@@ -122,7 +123,7 @@
122123
<dependency>
123124
<groupId>de.dfki.mary</groupId>
124125
<artifactId>voice-cmu-slt-hsmm</artifactId>
125-
<version>5.2.1</version>
126+
<version>${dfkimarytts.version}</version>
126127
<scope>compile</scope>
127128
</dependency>
128129
<dependency>
@@ -131,6 +132,18 @@
131132
<version>1.0.3</version>
132133
<scope>compile</scope>
133134
</dependency>
135+
<dependency>
136+
<groupId>org.apache.logging.log4j</groupId>
137+
<artifactId>log4j-1.2-api</artifactId>
138+
<version>2.25.2</version>
139+
<scope>test</scope>
140+
</dependency>
141+
<dependency>
142+
<groupId>org.apache.logging.log4j</groupId>
143+
<artifactId>log4j-core</artifactId>
144+
<version>2.25.2</version>
145+
<scope>test</scope>
146+
</dependency>
134147
<dependency>
135148
<groupId>org.apache.opennlp</groupId>
136149
<artifactId>opennlp-maxent</artifactId>
@@ -140,7 +153,7 @@
140153
<dependency>
141154
<groupId>org.apache.opennlp</groupId>
142155
<artifactId>opennlp-tools</artifactId>
143-
<version>1.5.3</version>
156+
<version>2.5.11</version>
144157
<scope>compile</scope>
145158
</dependency>
146159
<dependency>
@@ -188,6 +201,25 @@ SPI-Consumer: java.util.ServiceLoader#load(java.lang.Class[marytts.config.MaryCo
188201
</plugin>
189202
</plugins>
190203
</pluginManagement>
204+
<plugins>
205+
<plugin>
206+
<groupId>org.codehaus.mojo</groupId>
207+
<artifactId>build-helper-maven-plugin</artifactId>
208+
<executions>
209+
<execution>
210+
<goals>
211+
<goal>add-source</goal>
212+
</goals>
213+
<phase>generate-sources</phase>
214+
<configuration>
215+
<sources>
216+
<source>src/3rdparty/java</source>
217+
</sources>
218+
</configuration>
219+
</execution>
220+
</executions>
221+
</plugin>
222+
</plugins>
191223
</build>
192224

193225
</project>
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/**
2+
* Copyright 2007 DFKI GmbH.
3+
* All Rights Reserved. Use is subject to license terms.
4+
*
5+
* This file is part of MARY TTS.
6+
*
7+
* MARY TTS is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU Lesser General Public License as published by
9+
* the Free Software Foundation, version 3 of the License.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*
19+
*/
20+
package marytts.modules;
21+
22+
import static java.nio.charset.StandardCharsets.UTF_8;
23+
24+
import java.io.BufferedReader;
25+
import java.io.InputStream;
26+
import java.io.InputStreamReader;
27+
import java.util.ArrayList;
28+
import java.util.HashMap;
29+
import java.util.List;
30+
import java.util.Map;
31+
import java.util.StringTokenizer;
32+
33+
import org.eclipse.jdt.annotation.NonNullByDefault;
34+
import org.eclipse.jdt.annotation.Nullable;
35+
import org.slf4j.Logger;
36+
import org.slf4j.LoggerFactory;
37+
import org.w3c.dom.Document;
38+
import org.w3c.dom.Element;
39+
import org.w3c.dom.traversal.NodeIterator;
40+
import org.w3c.dom.traversal.TreeWalker;
41+
42+
import marytts.datatypes.MaryData;
43+
import marytts.datatypes.MaryDataType;
44+
import marytts.server.MaryProperties;
45+
import marytts.util.MaryUtils;
46+
import marytts.util.dom.MaryDomUtils;
47+
import opennlp.tools.postag.POSModel;
48+
import opennlp.tools.postag.POSTagFormat;
49+
import opennlp.tools.postag.POSTaggerME;
50+
51+
/**
52+
* OpenNLP POS tagger compatible with the model and tag set expected by MaryTTS 5.2.1.
53+
*
54+
* <p>
55+
* MaryTTS 5.2.1 uses OpenNLP's removed {@code tag(List)} API and the legacy one-argument constructor. This replacement
56+
* uses the current array API and explicitly retains Penn Treebank output, on which MaryTTS's English prosody rules
57+
* depend.
58+
*
59+
* @author Marc Schröder - Initial contribution
60+
* @author Leo Siepel - Refactored for use with openHAB's MaryTTS runtime
61+
*/
62+
@NonNullByDefault
63+
public class OpenNLPPosTagger extends InternalModule {
64+
65+
private static final Logger LOGGER = LoggerFactory.getLogger(OpenNLPPosTagger.class);
66+
67+
private final String propertyPrefix;
68+
69+
private @Nullable POSTaggerME tagger;
70+
private @Nullable Map<String, String> posMapper;
71+
72+
public OpenNLPPosTagger(String locale, String propertyPrefix) throws Exception {
73+
super("OpenNLPPosTagger", MaryDataType.WORDS, MaryDataType.PARTSOFSPEECH, MaryUtils.string2locale(locale));
74+
this.propertyPrefix = propertyPrefix.endsWith(".") ? propertyPrefix : propertyPrefix + ".";
75+
}
76+
77+
@Override
78+
public void startup() throws Exception {
79+
super.startup();
80+
81+
try (InputStream modelStream = MaryProperties.needStream(propertyPrefix + "model")) {
82+
tagger = new POSTaggerME(new POSModel(modelStream), POSTagFormat.PENN);
83+
}
84+
85+
InputStream posMapStream = MaryProperties.getStream(propertyPrefix + "posMap");
86+
if (posMapStream != null) {
87+
posMapper = readPosMapper(posMapStream);
88+
}
89+
}
90+
91+
private Map<String, String> readPosMapper(InputStream posMapStream) throws Exception {
92+
Map<String, String> mapper = new HashMap<>();
93+
try (posMapStream; BufferedReader reader = new BufferedReader(new InputStreamReader(posMapStream, UTF_8))) {
94+
String line;
95+
while ((line = reader.readLine()) != null) {
96+
if (!line.startsWith("#") && !line.isBlank()) {
97+
StringTokenizer tokenizer = new StringTokenizer(line);
98+
if (tokenizer.countTokens() >= 2) {
99+
mapper.put(tokenizer.nextToken(), tokenizer.nextToken());
100+
} else {
101+
LOGGER.warn("Invalid POS map line (expected 2 tokens): '{}'", line);
102+
}
103+
}
104+
}
105+
}
106+
return mapper;
107+
}
108+
109+
@Override
110+
public MaryData process(@NonNullByDefault({}) MaryData input) throws Exception {
111+
Document document = input.getDocument();
112+
NodeIterator sentenceIterator = MaryDomUtils.createNodeIterator(document, document, "s");
113+
114+
Element sentence;
115+
while ((sentence = (Element) sentenceIterator.nextNode()) != null) {
116+
tagSentence(sentence);
117+
}
118+
119+
MaryData output = new MaryData(getOutputType(), input.getLocale());
120+
output.setDocument(document);
121+
return output;
122+
}
123+
124+
private void tagSentence(Element sentence) {
125+
TreeWalker tokenWalker = MaryDomUtils.createTreeWalker(sentence, "t");
126+
List<Element> tokens = new ArrayList<>();
127+
List<String> words = new ArrayList<>();
128+
129+
Element token;
130+
while ((token = (Element) tokenWalker.nextNode()) != null) {
131+
tokens.add(token);
132+
words.add(MaryDomUtils.tokenText(token));
133+
}
134+
135+
// OpenNLP cannot determine useful context from a one-token sentence.
136+
if (words.size() == 1) {
137+
words.add(".");
138+
}
139+
140+
String[] tags;
141+
synchronized (this) {
142+
POSTaggerME tagger = this.tagger;
143+
if (tagger != null) {
144+
tags = tagger.tag(words.toArray(String[]::new));
145+
} else {
146+
tags = new String[words.size()];
147+
}
148+
}
149+
150+
for (int i = 0; i < tokens.size(); i++) {
151+
Element currentToken = tokens.get(i);
152+
if (!currentToken.hasAttribute("pos")) {
153+
currentToken.setAttribute("pos", mapPosTag(tags[i]));
154+
}
155+
}
156+
}
157+
158+
private String mapPosTag(String posTag) {
159+
Map<String, String> posMapper = this.posMapper;
160+
if (posMapper == null) {
161+
return posTag;
162+
}
163+
164+
String mappedTag = posMapper.get(posTag);
165+
if (mappedTag == null) {
166+
LOGGER.warn("POS map file incomplete: do not know how to map '{}'", posTag);
167+
return posTag;
168+
}
169+
return mappedTag;
170+
}
171+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright (c) 2010-2026 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.voice.marytts.internal;
14+
15+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
16+
import static org.junit.jupiter.api.Assertions.assertTrue;
17+
18+
import java.util.Locale;
19+
import java.util.stream.IntStream;
20+
21+
import javax.sound.sampled.AudioInputStream;
22+
23+
import org.junit.jupiter.api.BeforeAll;
24+
import org.junit.jupiter.api.Test;
25+
import org.w3c.dom.Document;
26+
import org.w3c.dom.Element;
27+
import org.w3c.dom.NodeList;
28+
29+
import marytts.LocalMaryInterface;
30+
import marytts.MaryInterface;
31+
import marytts.util.dom.MaryDomUtils;
32+
33+
/**
34+
* Runtime compatibility tests for MaryTTS and its upgraded dependencies.
35+
*
36+
* @author Leo Siepel - Initial contribution
37+
*/
38+
public class MaryTTSRuntimeTest {
39+
40+
private static MaryInterface marytts;
41+
42+
@BeforeAll
43+
public static void setUp() throws Exception {
44+
System.setProperty("mary.base", "target");
45+
marytts = new LocalMaryInterface();
46+
marytts.setLocale(Locale.US);
47+
marytts.setInputType("TEXT");
48+
}
49+
50+
@Test
51+
public void posTaggerRetainsPennTreebankTags() throws Exception {
52+
marytts.setOutputType("PARTSOFSPEECH");
53+
54+
Document document = marytts.generateXML("I want to go to the store.");
55+
NodeList tokens = document.getElementsByTagName("t");
56+
String[] words = IntStream.range(0, tokens.getLength())
57+
.mapToObj(i -> MaryDomUtils.tokenText((Element) tokens.item(i))).toArray(String[]::new);
58+
String[] tags = IntStream.range(0, tokens.getLength())
59+
.mapToObj(i -> ((Element) tokens.item(i)).getAttribute("pos")).toArray(String[]::new);
60+
61+
assertArrayEquals(new String[] { "I", "want", "to", "go", "to", "the", "store", "." }, words);
62+
assertArrayEquals(new String[] { "PRP", "VBP", "TO", "VB", "TO", "DT", "NN", "." }, tags);
63+
}
64+
65+
@Test
66+
public void englishSynthesisProducesAudio() throws Exception {
67+
marytts.setOutputType("AUDIO");
68+
69+
try (AudioInputStream audio = marytts.generateAudio("This is a synthesis test.")) {
70+
int frameSize = Math.max(1, audio.getFormat().getFrameSize());
71+
byte[] audioData = new byte[frameSize * 256];
72+
assertTrue(audio.read(audioData) > 0);
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)