Skip to content

Commit 7c6e658

Browse files
[openaitts] OpenAI Text-to-Speech initial contribution (openhab#17733)
Also-by: Wouter Born <[email protected]> Signed-off-by: Artur-Fedjukevits <[email protected]>
1 parent 50e3ca6 commit 7c6e658

File tree

14 files changed

+408
-0
lines changed

14 files changed

+408
-0
lines changed

CODEOWNERS

+1
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,7 @@
465465
/bundles/org.openhab.voice.mactts/ @kaikreuzer
466466
/bundles/org.openhab.voice.marytts/ @kaikreuzer
467467
/bundles/org.openhab.voice.mimictts/ @dalgwen
468+
/bundles/org.openhab.voice.openaitts/ @Artur-Fedjukevits
468469
/bundles/org.openhab.voice.picotts/ @FlorianSW
469470
/bundles/org.openhab.voice.pipertts/ @GiviMAD
470471
/bundles/org.openhab.voice.pollytts/ @openhab/add-ons-maintainers

bom/openhab-addons/pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -2301,6 +2301,11 @@
23012301
<artifactId>org.openhab.voice.mimictts</artifactId>
23022302
<version>${project.version}</version>
23032303
</dependency>
2304+
<dependency>
2305+
<groupId>org.openhab.addons.bundles</groupId>
2306+
<artifactId>org.openhab.voice.openaitts</artifactId>
2307+
<version>${project.version}</version>
2308+
</dependency>
23042309
<dependency>
23052310
<groupId>org.openhab.addons.bundles</groupId>
23062311
<artifactId>org.openhab.voice.picotts</artifactId>
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
This content is produced and maintained by the openHAB project.
2+
3+
* Project home: https://www.openhab.org
4+
5+
== Declared Project Licenses
6+
7+
This program and the accompanying materials are made available under the terms
8+
of the Eclipse Public License 2.0 which is available at
9+
https://www.eclipse.org/legal/epl-2.0/.
10+
11+
== Source Code
12+
13+
https://github.com/openhab/openhab-addons
14+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# OpenAI Text-to-Speech
2+
3+
The OpenAI TTS (Text-to-Speech) add-on for openHAB allows you to integrate OpenAI's Text-to-Speech capabilities into your openHAB system.
4+
The advantage of this service over others is that one selected voice can speak different languages.
5+
This is useful, for example, in conjunction with ChatGPT binding, which will help in learning foreign languages.
6+
You can find the price for this service here - https://openai.com/api/pricing/
7+
8+
## Configuration
9+
10+
To configure the OpenAI TTS, **Settings / Other Services - OpenAI Text-to-Speech** and set:
11+
12+
* **apiKey** - The API key to be used for the requests.
13+
* **apiUrl** - The server API where to reach the AI TTS service.
14+
* **model** - The ID of the model to use for TTS.
15+
16+
### Default Text-to-Speech and Voice Configuration
17+
18+
You can setup your preferred default Text-to-Speech and default voice in the UI:
19+
20+
* Go to **Settings**.
21+
* Edit **System Services - Voice**.
22+
* Set **OpenAI TTS Service** as **Default Text-to-Speech**.
23+
* Choose your preferred **Default Voice** for your setup.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>org.openhab.addons.bundles</groupId>
9+
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
10+
<version>5.0.0-SNAPSHOT</version>
11+
</parent>
12+
13+
<artifactId>org.openhab.voice.openaitts</artifactId>
14+
15+
<name>openHAB Add-ons :: Bundles :: Voice :: OpenAI Text-to-Speech</name>
16+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<features name="org.openhab.voice.openaitts-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
3+
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
4+
5+
<feature name="openhab-voice-openaitts" description="OpenAI Text-to-Speech" version="${project.version}">
6+
<feature>openhab-runtime-base</feature>
7+
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.voice.openaitts/${project.version}</bundle>
8+
</feature>
9+
</features>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* Copyright (c) 2010-2024 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.openaitts.internal;
14+
15+
import org.eclipse.jdt.annotation.NonNullByDefault;
16+
17+
/**
18+
* @author Artur Fedjukevits - Initial contribution
19+
*/
20+
@NonNullByDefault
21+
public class OpenAITTSConfiguration {
22+
23+
public String apiKey = "";
24+
public String apiUrl = "https://api.openai.com/v1/audio/speech";
25+
public String model = "tts-1";
26+
public String speed = "1";
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Copyright (c) 2010-2024 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.openaitts.internal;
14+
15+
import org.eclipse.jdt.annotation.NonNullByDefault;
16+
17+
/**
18+
* @author Artur Fedjukevits - Initial contribution
19+
*/
20+
@NonNullByDefault
21+
public class OpenAITTSConstants {
22+
23+
public static final String TTS_SERVICE_ID = "openaitts";
24+
public static final String TTS_SERVICE_PID = "org.openhab.voice.openaitts";
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/**
2+
* Copyright (c) 2010-2024 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.openaitts.internal;
14+
15+
import static org.openhab.voice.openaitts.internal.OpenAITTSConstants.*;
16+
17+
import java.util.Locale;
18+
import java.util.Map;
19+
import java.util.Set;
20+
import java.util.concurrent.ExecutionException;
21+
import java.util.concurrent.TimeUnit;
22+
import java.util.concurrent.TimeoutException;
23+
import java.util.stream.Collectors;
24+
import java.util.stream.Stream;
25+
26+
import org.eclipse.jdt.annotation.NonNullByDefault;
27+
import org.eclipse.jdt.annotation.Nullable;
28+
import org.eclipse.jetty.client.HttpClient;
29+
import org.eclipse.jetty.client.api.ContentResponse;
30+
import org.eclipse.jetty.client.util.StringContentProvider;
31+
import org.eclipse.jetty.http.HttpMethod;
32+
import org.eclipse.jetty.http.HttpStatus;
33+
import org.openhab.core.audio.AudioFormat;
34+
import org.openhab.core.audio.AudioStream;
35+
import org.openhab.core.audio.ByteArrayAudioStream;
36+
import org.openhab.core.config.core.ConfigurableService;
37+
import org.openhab.core.config.core.Configuration;
38+
import org.openhab.core.io.net.http.HttpClientFactory;
39+
import org.openhab.core.voice.AbstractCachedTTSService;
40+
import org.openhab.core.voice.TTSCache;
41+
import org.openhab.core.voice.TTSException;
42+
import org.openhab.core.voice.TTSService;
43+
import org.openhab.core.voice.Voice;
44+
import org.osgi.framework.Constants;
45+
import org.osgi.service.component.annotations.Activate;
46+
import org.osgi.service.component.annotations.Component;
47+
import org.osgi.service.component.annotations.Modified;
48+
import org.osgi.service.component.annotations.Reference;
49+
import org.slf4j.Logger;
50+
import org.slf4j.LoggerFactory;
51+
52+
import com.google.gson.Gson;
53+
import com.google.gson.JsonObject;
54+
55+
/**
56+
* @author Artur Fedjukevits - Initial contribution
57+
* API documentation: https://platform.openai.com/docs/guides/text-to-speech
58+
*/
59+
@Component(configurationPid = TTS_SERVICE_PID, property = Constants.SERVICE_PID + "="
60+
+ TTS_SERVICE_PID, service = TTSService.class)
61+
@ConfigurableService(category = "voice", label = "OpenAI TTS Service", description_uri = "voice:" + TTS_SERVICE_ID)
62+
63+
@NonNullByDefault
64+
public class OpenAITTSService extends AbstractCachedTTSService {
65+
66+
private static final int REQUEST_TIMEOUT_MS = 10_000;
67+
private final Logger logger = LoggerFactory.getLogger(OpenAITTSService.class);
68+
private OpenAITTSConfiguration config = new OpenAITTSConfiguration();
69+
private final HttpClient httpClient;
70+
private final Gson gson = new Gson();
71+
private static final Set<Voice> VOICES = Stream.of("nova", "alloy", "echo", "fable", "onyx", "shimmer")
72+
.map(OpenAITTSVoice::new).collect(Collectors.toSet());
73+
74+
@Activate
75+
public OpenAITTSService(@Reference HttpClientFactory httpClientFactory, @Reference TTSCache ttsCache,
76+
Map<String, Object> config) {
77+
super(ttsCache);
78+
this.httpClient = httpClientFactory.getCommonHttpClient();
79+
}
80+
81+
@Activate
82+
protected void activate(Map<String, Object> config) {
83+
this.config = new Configuration(config).as(OpenAITTSConfiguration.class);
84+
}
85+
86+
@Modified
87+
protected void modified(Map<String, Object> config) {
88+
this.config = new Configuration(config).as(OpenAITTSConfiguration.class);
89+
}
90+
91+
@Override
92+
public Set<AudioFormat> getSupportedFormats() {
93+
return Set.of(new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_MP3, null, 16, 64000, 44100L));
94+
}
95+
96+
@Override
97+
public String getId() {
98+
return TTS_SERVICE_ID;
99+
}
100+
101+
@Override
102+
public String getLabel(@Nullable Locale locale) {
103+
return "OpenAI TTS Service";
104+
}
105+
106+
@Override
107+
public Set<Voice> getAvailableVoices() {
108+
return VOICES;
109+
}
110+
111+
/**
112+
* Synthesizes the given text to audio data using the OpenAI API
113+
*
114+
* @param text The text to synthesize
115+
* @param voice The voice to use
116+
* @param requestedFormat The requested audio format
117+
* @return The synthesized audio data
118+
* @throws TTSException If the synthesis fails
119+
*/
120+
@Override
121+
public AudioStream synthesizeForCache(String text, Voice voice, AudioFormat requestedFormat) throws TTSException {
122+
JsonObject content = new JsonObject();
123+
content.addProperty("model", config.model);
124+
content.addProperty("input", text);
125+
content.addProperty("voice", voice.getLabel().toLowerCase());
126+
content.addProperty("speed", config.speed);
127+
128+
String queryJson = gson.toJson(content);
129+
130+
try {
131+
ContentResponse response = httpClient.newRequest(config.apiUrl).method(HttpMethod.POST)
132+
.timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS)
133+
.header("Authorization", "Bearer " + config.apiKey).header("Content-Type", "application/json")
134+
.content(new StringContentProvider(queryJson)).send();
135+
136+
if (response.getStatus() == HttpStatus.OK_200) {
137+
return new ByteArrayAudioStream(response.getContent(), requestedFormat);
138+
} else {
139+
logger.error("Request resulted in HTTP {} with message: {}", response.getStatus(),
140+
response.getReason());
141+
throw new TTSException("Failed to generate audio data");
142+
}
143+
} catch (InterruptedException | TimeoutException | ExecutionException e) {
144+
logger.error("Request to OpenAI failed: {}", e.getMessage(), e);
145+
throw new TTSException("Failed to generate audio data");
146+
}
147+
}
148+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* Copyright (c) 2010-2024 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.openaitts.internal;
14+
15+
import java.util.Locale;
16+
17+
import org.eclipse.jdt.annotation.NonNullByDefault;
18+
import org.openhab.core.voice.Voice;
19+
20+
/**
21+
* @author Artur Fedjukevits - Initial contribution
22+
*/
23+
@NonNullByDefault
24+
public class OpenAITTSVoice implements Voice {
25+
26+
private final String label;
27+
28+
public OpenAITTSVoice(String label) {
29+
this.label = label;
30+
}
31+
32+
/**
33+
* The unique identifier of the voice, used for internal purposes
34+
*
35+
* @return The unique identifier of the voice
36+
*/
37+
@Override
38+
public String getUID() {
39+
return "openaitts:" + label;
40+
}
41+
42+
/**
43+
* The voice label, used for GUI's or VUI's
44+
*
45+
* @return The voice label
46+
*/
47+
@Override
48+
public String getLabel() {
49+
return Character.toUpperCase(label.charAt(0)) + label.substring(1);
50+
}
51+
52+
/**
53+
* The locale of the voice
54+
*
55+
* @return The locale of the voice
56+
*/
57+
@Override
58+
public Locale getLocale() {
59+
return Locale.ENGLISH;
60+
}
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<addon:addon id="openaitts" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
4+
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
5+
6+
<type>voice</type>
7+
<name>OpenAI Text-to-Speech</name>
8+
<description>OpenAI TTS Service provides text-to-speech capabilities for openHAB.</description>
9+
<connection>cloud</connection>
10+
11+
<service-id>org.openhab.voice.openaitts</service-id>
12+
13+
<config-description-ref uri="voice:openaitts"/>
14+
15+
</addon:addon>

0 commit comments

Comments
 (0)