mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-25 14:55:55 +01:00
[PiperTTS] Reduce bundle size (#16755)
* [PiperTTS] Reduce bundle size Signed-off-by: Miguel Álvarez <miguelwork92@gmail.com> Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
8c3e107868
commit
49da1f7995
@ -13,6 +13,12 @@ The add-on is compatible with the following platforms:
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
## Setting up dependencies
|
||||||
|
|
||||||
|
The add-on will download the required dependencies at first activation.
|
||||||
|
|
||||||
|
If your OpenHAB installation do not have access to the internet, you need to download the [piper-jni jar file](https://repo1.maven.org/maven2/io/github/givimad/piper-jni/1.2.0-a0f09cd/piper-jni-1.2.0-a0f09cd.jar) and place it at '<OPENHAB_USERDATA>/piper/'.
|
||||||
|
|
||||||
### Downloading Voice Model Files
|
### Downloading Voice Model Files
|
||||||
|
|
||||||
You can find the link to the available voices at the [Piper README](https://github.com/rhasspy/piper).
|
You can find the link to the available voices at the [Piper README](https://github.com/rhasspy/piper).
|
||||||
|
@ -18,7 +18,26 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.github.givimad</groupId>
|
<groupId>io.github.givimad</groupId>
|
||||||
<artifactId>piper-jni</artifactId>
|
<artifactId>piper-jni</artifactId>
|
||||||
<version>1.2.0-e5cb84c</version>
|
<version>1.2.0-a0f09cd</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<excludes>
|
||||||
|
<exclude>**/win-*/**</exclude>
|
||||||
|
<exclude>**/debian-*/**</exclude>
|
||||||
|
<exclude>**/macos-*/**</exclude>
|
||||||
|
<exclude>**/espeak-ng-data.zip</exclude>
|
||||||
|
<exclude>**/libtashkeel_model.ort</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
@ -20,11 +20,15 @@ import static org.openhab.voice.pipertts.internal.PiperTTSConstants.SERVICE_PID;
|
|||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Enumeration;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@ -32,7 +36,10 @@ import java.util.Map;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.sound.sampled.AudioFileFormat;
|
import javax.sound.sampled.AudioFileFormat;
|
||||||
@ -45,6 +52,7 @@ import org.openhab.core.OpenHAB;
|
|||||||
import org.openhab.core.audio.AudioFormat;
|
import org.openhab.core.audio.AudioFormat;
|
||||||
import org.openhab.core.audio.AudioStream;
|
import org.openhab.core.audio.AudioStream;
|
||||||
import org.openhab.core.audio.ByteArrayAudioStream;
|
import org.openhab.core.audio.ByteArrayAudioStream;
|
||||||
|
import org.openhab.core.common.ThreadPoolManager;
|
||||||
import org.openhab.core.config.core.ConfigurableService;
|
import org.openhab.core.config.core.ConfigurableService;
|
||||||
import org.openhab.core.config.core.Configuration;
|
import org.openhab.core.config.core.Configuration;
|
||||||
import org.openhab.core.voice.AbstractCachedTTSService;
|
import org.openhab.core.voice.AbstractCachedTTSService;
|
||||||
@ -78,13 +86,25 @@ import io.github.givimad.piperjni.PiperVoice;
|
|||||||
@ConfigurableService(category = SERVICE_CATEGORY, label = SERVICE_NAME
|
@ConfigurableService(category = SERVICE_CATEGORY, label = SERVICE_NAME
|
||||||
+ " Text-to-Speech", description_uri = SERVICE_CATEGORY + ":" + SERVICE_ID)
|
+ " Text-to-Speech", description_uri = SERVICE_CATEGORY + ":" + SERVICE_ID)
|
||||||
public class PiperTTSService extends AbstractCachedTTSService {
|
public class PiperTTSService extends AbstractCachedTTSService {
|
||||||
|
// piper-jni version from pom.xml
|
||||||
|
private static final String PIPER_VERSION = "1.2.0-a0f09cd";
|
||||||
private static final Path PIPER_FOLDER = Path.of(OpenHAB.getUserDataFolder(), "piper");
|
private static final Path PIPER_FOLDER = Path.of(OpenHAB.getUserDataFolder(), "piper");
|
||||||
|
private static final Path LIB_FOLDER = PIPER_FOLDER.resolve("lib-" + PIPER_VERSION);
|
||||||
|
private static final Path JAR_FILE = PIPER_FOLDER.resolve("piper-jni-" + PIPER_VERSION + ".jar");
|
||||||
|
private static final String JAR_URL = "https://repo1.maven.org/maven2/io/github/givimad/piper-jni/" + PIPER_VERSION
|
||||||
|
+ "/piper-jni-" + PIPER_VERSION + ".jar";
|
||||||
private final Logger logger = LoggerFactory.getLogger(PiperTTSService.class);
|
private final Logger logger = LoggerFactory.getLogger(PiperTTSService.class);
|
||||||
private final Object modelLock = new Object();
|
private final Object modelLock = new Object();
|
||||||
|
private final ExecutorService executor = ThreadPoolManager.getPool("voice-pipertts");
|
||||||
private PiperTTSConfiguration config = new PiperTTSConfiguration();
|
private PiperTTSConfiguration config = new PiperTTSConfiguration();
|
||||||
|
private Map<String, List<Voice>> cachedVoicesByModel = new HashMap<>();
|
||||||
|
private boolean ready = false;
|
||||||
private @Nullable VoiceModel preloadedModel;
|
private @Nullable VoiceModel preloadedModel;
|
||||||
private @Nullable PiperJNI piper;
|
private @Nullable PiperJNI piper;
|
||||||
private Map<String, List<Voice>> cachedVoicesByModel = new HashMap<>();
|
private @Nullable Future<?> activateTask;
|
||||||
|
static {
|
||||||
|
System.setProperty("io.github.givimad.piperjni.libdir", LIB_FOLDER.toAbsolutePath().toString());
|
||||||
|
}
|
||||||
|
|
||||||
@Activate
|
@Activate
|
||||||
public PiperTTSService(final @Reference TTSCache ttsCache) {
|
public PiperTTSService(final @Reference TTSCache ttsCache) {
|
||||||
@ -93,17 +113,89 @@ public class PiperTTSService extends AbstractCachedTTSService {
|
|||||||
|
|
||||||
@Activate
|
@Activate
|
||||||
protected void activate(Map<String, Object> config) {
|
protected void activate(Map<String, Object> config) {
|
||||||
try {
|
|
||||||
piper = new PiperJNI();
|
|
||||||
piper.initialize(true, false);
|
|
||||||
logger.debug("Using Piper version {}", piper.getPiperVersion());
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.warn("Piper registration failed, the add-on will not work: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
tryCreatePiperDirectory();
|
tryCreatePiperDirectory();
|
||||||
|
activateTask = executor.submit(() -> {
|
||||||
|
try {
|
||||||
|
setupNativeDependencies();
|
||||||
|
piper = new PiperJNI();
|
||||||
|
piper.initialize(true, false);
|
||||||
|
logger.debug("Using Piper version {}", piper.getPiperVersion());
|
||||||
|
ready = true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Piper registration failed, the add-on will not work: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
configChange(config);
|
configChange(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deactivate
|
||||||
|
private void deactivate() {
|
||||||
|
if (activateTask != null && !activateTask.isDone()) {
|
||||||
|
activateTask.cancel(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupNativeDependencies() throws IOException {
|
||||||
|
String folderName = "";
|
||||||
|
String osName = System.getProperty("os.name").toLowerCase();
|
||||||
|
String osArch = System.getProperty("os.arch").toLowerCase();
|
||||||
|
if (osName.contains("win")) {
|
||||||
|
if (osArch.contains("amd64") || osArch.contains("x86_64")) {
|
||||||
|
folderName = "win-amd64";
|
||||||
|
}
|
||||||
|
} else if (osName.contains("nix") || osName.contains("nux") || osName.contains("aix")) {
|
||||||
|
if (osArch.contains("amd64") || osArch.contains("x86_64")) {
|
||||||
|
folderName = "debian-amd64";
|
||||||
|
} else if (osArch.contains("aarch64") || osArch.contains("arm64")) {
|
||||||
|
folderName = "debian-arm64";
|
||||||
|
} else if (osArch.contains("armv7") || osArch.contains("arm")) {
|
||||||
|
folderName = "debian-armv7l";
|
||||||
|
}
|
||||||
|
} else if (osName.contains("mac") || osName.contains("darwin")) {
|
||||||
|
if (osArch.contains("amd64") || osArch.contains("x86_64")) {
|
||||||
|
folderName = "macos-amd64";
|
||||||
|
} else if (osArch.contains("aarch64") || osArch.contains("arm64")) {
|
||||||
|
folderName = "macos-arm64";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (folderName.isBlank()) {
|
||||||
|
throw new IOException("Incompatible platform, unable to setup add-on");
|
||||||
|
}
|
||||||
|
if (!Files.exists(LIB_FOLDER)) {
|
||||||
|
Files.createDirectory(LIB_FOLDER);
|
||||||
|
}
|
||||||
|
if (!Files.exists(JAR_FILE)) {
|
||||||
|
logger.debug("Downloading file: {}", JAR_URL);
|
||||||
|
InputStream in = new URL(JAR_URL).openStream();
|
||||||
|
Files.copy(in, JAR_FILE, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
try (java.util.jar.JarFile jar = new java.util.jar.JarFile(JAR_FILE.toFile())) {
|
||||||
|
Enumeration<JarEntry> enumEntries = jar.entries();
|
||||||
|
while (enumEntries.hasMoreElements()) {
|
||||||
|
java.util.jar.JarEntry file = enumEntries.nextElement();
|
||||||
|
String filename = file.getName();
|
||||||
|
if (!filename.startsWith(folderName) && !"espeak-ng-data.zip".equals(filename)
|
||||||
|
&& !"libtashkeel_model.ort".equals(filename)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Path targetPath = LIB_FOLDER.resolve(file.getName());
|
||||||
|
if (Files.exists(targetPath)) {
|
||||||
|
logger.debug("Found piper native dependency: {}", file.getName());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
logger.debug("Creating dir: {}", targetPath);
|
||||||
|
Files.createDirectory(targetPath);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
logger.debug("Extracting piper native dependency: {}", file.getName());
|
||||||
|
try (var is = jar.getInputStream(file)) {
|
||||||
|
Files.copy(is, targetPath, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Modified
|
@Modified
|
||||||
protected void modified(Map<String, Object> config) {
|
protected void modified(Map<String, Object> config) {
|
||||||
configChange(config);
|
configChange(config);
|
||||||
@ -241,6 +333,9 @@ public class PiperTTSService extends AbstractCachedTTSService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AudioStream synthesizeForCache(String text, Voice voice, AudioFormat audioFormat) throws TTSException {
|
public AudioStream synthesizeForCache(String text, Voice voice, AudioFormat audioFormat) throws TTSException {
|
||||||
|
if (!ready) {
|
||||||
|
throw new TTSException("Add-on is not loaded");
|
||||||
|
}
|
||||||
if (!(voice instanceof PiperTTSVoice ttsVoice)) {
|
if (!(voice instanceof PiperTTSVoice ttsVoice)) {
|
||||||
throw new TTSException("No piper voice provided");
|
throw new TTSException("No piper voice provided");
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user