diff --git a/core/src/main/java/dev/felnull/itts/core/voice/VoiceHttpUtils.java b/core/src/main/java/dev/felnull/itts/core/voice/VoiceHttpUtils.java new file mode 100644 index 0000000..cfb0cfd --- /dev/null +++ b/core/src/main/java/dev/felnull/itts/core/voice/VoiceHttpUtils.java @@ -0,0 +1,39 @@ +package dev.felnull.itts.core.voice; + +import java.io.IOException; +import java.net.http.HttpTimeoutException; +import java.time.Duration; +import java.time.temporal.ChronoUnit; + +/** + * 音声合成APIのHTTP処理で使う共通値と例外生成 + * + * @author Codex + */ +public final class VoiceHttpUtils { + + /** + * 話者一覧取得のタイムアウト + */ + public static final Duration SPEAKER_LIST_TIMEOUT = Duration.of(3000, ChronoUnit.MILLIS); + + /** + * 音声合成リクエストのタイムアウト + */ + public static final Duration SYNTHESIS_TIMEOUT = Duration.of(30, ChronoUnit.SECONDS); + + private VoiceHttpUtils() { + } + + /** + * タイムアウト例外を音声API名つきのIO例外へ変換する + * + * @param engineName エンジン名 + * @param apiName API名 + * @param exception タイムアウト例外 + * @return IO例外 + */ + public static IOException timeoutException(String engineName, String apiName, HttpTimeoutException exception) { + return new IOException(engineName + " " + apiName + " API timed out after " + SYNTHESIS_TIMEOUT.toSeconds() + " seconds", exception); + } +} diff --git a/core/src/main/java/dev/felnull/itts/core/voice/coeiroink/CoeiroinkManager.java b/core/src/main/java/dev/felnull/itts/core/voice/coeiroink/CoeiroinkManager.java index a1e233d..6d292c7 100644 --- a/core/src/main/java/dev/felnull/itts/core/voice/coeiroink/CoeiroinkManager.java +++ b/core/src/main/java/dev/felnull/itts/core/voice/coeiroink/CoeiroinkManager.java @@ -7,15 +7,15 @@ import com.google.gson.JsonObject; import dev.felnull.itts.core.ITTSRuntime; import dev.felnull.itts.core.config.voicetype.VoicevoxConfig; +import dev.felnull.itts.core.voice.VoiceHttpUtils; import dev.felnull.itts.core.voice.VoiceType; import java.io.*; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.net.http.HttpTimeoutException; import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -115,7 +115,7 @@ protected CoeiroinkBalancer getBalancer() { protected List requestSpeakers(CIURL ciurl) throws IOException, InterruptedException { HttpClient hc = ITTSRuntime.getInstance().getNetworkManager().getHttpClient(); HttpRequest req = HttpRequest.newBuilder(ciurl.createURI("speakers")) - .timeout(Duration.of(3000, ChronoUnit.MILLIS)) + .timeout(VoiceHttpUtils.SPEAKER_LIST_TIMEOUT) .build(); HttpResponse rep = hc.send(req, HttpResponse.BodyHandlers.ofInputStream()); @@ -150,13 +150,15 @@ protected List requestSpeakers(CIURL ciurl) throws IOException * @param styleId スタイルID * @param speakerUuid スピーカーのUUID * @return 音声データのストリーム + * @throws IOException IO例外 + * @throws InterruptedException 割り込み例外 */ - protected InputStream openVoiceStream(String text, int styleId, String speakerUuid) { + protected InputStream openVoiceStream(String text, int styleId, String speakerUuid) throws IOException, InterruptedException { JsonObject qry = createSynthesisParam(text, styleId, speakerUuid); - try (var urlUse = balancer.getUseURL()) { + try (CoeiroinkUseURL urlUse = balancer.getUseURL()) { HttpClient hc = ITTSRuntime.getInstance().getNetworkManager().getHttpClient(); HttpRequest request = HttpRequest.newBuilder(urlUse.getCIURL().createURI("synthesis")) - .timeout(Duration.of(10, ChronoUnit.SECONDS)) + .timeout(VoiceHttpUtils.SYNTHESIS_TIMEOUT) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(GSON.toJson(qry))) .build(); @@ -176,8 +178,8 @@ protected InputStream openVoiceStream(String text, int styleId, String speakerUu } throw new IOException("Not audio data: " + code); - } catch (Exception e) { - throw new RuntimeException(e); + } catch (HttpTimeoutException e) { + throw VoiceHttpUtils.timeoutException(name, "synthesis", e); } } diff --git a/core/src/main/java/dev/felnull/itts/core/voice/coeiroink/CoeiroinkUseURL.java b/core/src/main/java/dev/felnull/itts/core/voice/coeiroink/CoeiroinkUseURL.java index ba65c60..7922cde 100644 --- a/core/src/main/java/dev/felnull/itts/core/voice/coeiroink/CoeiroinkUseURL.java +++ b/core/src/main/java/dev/felnull/itts/core/voice/coeiroink/CoeiroinkUseURL.java @@ -13,4 +13,10 @@ public interface CoeiroinkUseURL extends AutoCloseable { * @return CoeiroinkエンジンのURL */ CIURL getCIURL(); + + /** + * URL使用状態を解除 + */ + @Override + void close(); } diff --git a/core/src/main/java/dev/felnull/itts/core/voice/voicetext/VoiceTextManager.java b/core/src/main/java/dev/felnull/itts/core/voice/voicetext/VoiceTextManager.java index e08ee8f..5469ffc 100644 --- a/core/src/main/java/dev/felnull/itts/core/voice/voicetext/VoiceTextManager.java +++ b/core/src/main/java/dev/felnull/itts/core/voice/voicetext/VoiceTextManager.java @@ -5,6 +5,7 @@ import com.google.gson.JsonSyntaxException; import dev.felnull.fnjl.util.FNStringUtil; import dev.felnull.itts.core.ITTSRuntimeUse; +import dev.felnull.itts.core.voice.VoiceHttpUtils; import dev.felnull.itts.core.voice.VoiceType; import org.jetbrains.annotations.NotNull; @@ -14,6 +15,7 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.net.http.HttpTimeoutException; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; @@ -79,9 +81,16 @@ public InputStream openVoiceStream(@NotNull VoiceTextSpeaker speaker, @NotNull S HttpRequest request = HttpRequest.newBuilder(URI.create(API_URL)) .header("Authorization", basic) .header("Content-Type", "application/x-www-form-urlencoded; charset=utf-8") + .timeout(VoiceHttpUtils.SYNTHESIS_TIMEOUT) .POST(HttpRequest.BodyPublishers.ofString(String.format("text=%s&speaker=%s", text, speaker.getId()))) .build(); - HttpResponse res = hc.send(request, HttpResponse.BodyHandlers.ofInputStream()); + HttpResponse res; + + try { + res = hc.send(request, HttpResponse.BodyHandlers.ofInputStream()); + } catch (HttpTimeoutException e) { + throw VoiceHttpUtils.timeoutException("VoiceText", "tts", e); + } Optional content = res.headers().firstValue("content-type"); int code = res.statusCode(); diff --git a/core/src/main/java/dev/felnull/itts/core/voice/voicevox/VoicevoxManager.java b/core/src/main/java/dev/felnull/itts/core/voice/voicevox/VoicevoxManager.java index b607cd7..08c0355 100644 --- a/core/src/main/java/dev/felnull/itts/core/voice/voicevox/VoicevoxManager.java +++ b/core/src/main/java/dev/felnull/itts/core/voice/voicevox/VoicevoxManager.java @@ -7,6 +7,7 @@ import com.google.gson.JsonObject; import dev.felnull.itts.core.ITTSRuntime; import dev.felnull.itts.core.config.voicetype.VoicevoxConfig; +import dev.felnull.itts.core.voice.VoiceHttpUtils; import dev.felnull.itts.core.voice.VoiceType; import java.io.*; @@ -14,9 +15,8 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.net.http.HttpTimeoutException; import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -116,7 +116,7 @@ protected VoicevoxBalancer getBalancer() { protected List requestSpeakers(VVURL vvurl) throws IOException, InterruptedException { HttpClient hc = ITTSRuntime.getInstance().getNetworkManager().getHttpClient(); HttpRequest req = HttpRequest.newBuilder(vvurl.createURI("speakers")) - .timeout(Duration.of(3000, ChronoUnit.MILLIS)) + .timeout(VoiceHttpUtils.SPEAKER_LIST_TIMEOUT) .build(); HttpResponse rep = hc.send(req, HttpResponse.BodyHandlers.ofInputStream()); @@ -144,14 +144,14 @@ protected List requestSpeakers(VVURL vvurl) throws IOException, return speakerBuilder.build(); } - private JsonObject getQuery(String text, int speakerId) { + private JsonObject getQuery(String text, int speakerId) throws IOException, InterruptedException { text = URLEncoder.encode(text, StandardCharsets.UTF_8); - try (var urlUse = balancer.getUseURL()) { + try (VoicevoxUseURL urlUse = balancer.getUseURL()) { HttpClient hc = ITTSRuntime.getInstance().getNetworkManager().getHttpClient(); HttpRequest req = HttpRequest.newBuilder(urlUse.getVVURL().createURI(String.format("audio_query?text=%s&speaker=%d", text, speakerId))) .POST(HttpRequest.BodyPublishers.noBody()) - .timeout(Duration.of(10, ChronoUnit.SECONDS)) + .timeout(VoiceHttpUtils.SYNTHESIS_TIMEOUT) .build(); HttpResponse rep = hc.send(req, HttpResponse.BodyHandlers.ofInputStream()); @@ -170,8 +170,8 @@ private JsonObject getQuery(String text, int speakerId) { } return result; - } catch (Exception e) { - throw new RuntimeException(e); + } catch (HttpTimeoutException e) { + throw VoiceHttpUtils.timeoutException(name, "audio_query", e); } } @@ -186,10 +186,10 @@ private JsonObject getQuery(String text, int speakerId) { */ protected InputStream openVoiceStream(String text, int speakerId) throws IOException, InterruptedException { JsonObject qry = getQuery(text, speakerId); - try (var urlUse = balancer.getUseURL()) { + try (VoicevoxUseURL urlUse = balancer.getUseURL()) { HttpClient hc = ITTSRuntime.getInstance().getNetworkManager().getHttpClient(); HttpRequest request = HttpRequest.newBuilder(urlUse.getVVURL().createURI(String.format("synthesis?speaker=%d", speakerId))) - .timeout(Duration.of(10, ChronoUnit.SECONDS)) + .timeout(VoiceHttpUtils.SYNTHESIS_TIMEOUT) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(GSON.toJson(qry))) .build(); @@ -209,8 +209,8 @@ protected InputStream openVoiceStream(String text, int speakerId) throws IOExcep } throw new IOException("Not audio data: " + code); - } catch (Exception e) { - throw new RuntimeException(e); + } catch (HttpTimeoutException e) { + throw VoiceHttpUtils.timeoutException(name, "synthesis", e); } } } diff --git a/core/src/main/java/dev/felnull/itts/core/voice/voicevox/VoicevoxUseURL.java b/core/src/main/java/dev/felnull/itts/core/voice/voicevox/VoicevoxUseURL.java index 55ea639..1dd8091 100644 --- a/core/src/main/java/dev/felnull/itts/core/voice/voicevox/VoicevoxUseURL.java +++ b/core/src/main/java/dev/felnull/itts/core/voice/voicevox/VoicevoxUseURL.java @@ -13,4 +13,10 @@ public interface VoicevoxUseURL extends AutoCloseable { * @return VOICEVOXエンジンのURL */ VVURL getVVURL(); + + /** + * URL使用状態を解除 + */ + @Override + void close(); }