diff --git a/CHANGELOG.md b/CHANGELOG.md index 82b798cd..e42e2edc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,3 @@ -2022/05/26 +2022/08/16 -添加预览版语音,需要打开预览语音开关,当选择预览版语音时,如果卡住了,杀掉应用重进!!! \ No newline at end of file +普通模式不再支持风格调整 \ No newline at end of file diff --git a/app/src/main/java/me/ag2s/tts/MainActivity.java b/app/src/main/java/me/ag2s/tts/MainActivity.java index 7a6c424c..a22be18b 100644 --- a/app/src/main/java/me/ag2s/tts/MainActivity.java +++ b/app/src/main/java/me/ag2s/tts/MainActivity.java @@ -110,8 +110,12 @@ protected void onCreate(Bundle savedInstanceState) { binding.switchUseDict.setChecked(APP.getBoolean(Constants.USE_DICT, false)); binding.switchUseDict.setOnCheckedChangeListener((buttonView, isChecked) -> APP.putBoolean(Constants.USE_DICT, isChecked)); + showStyleView(APP.getBoolean(Constants.USE_PREVIEW, false)); binding.switchUsePreview.setChecked(APP.getBoolean(Constants.USE_PREVIEW, false)); - binding.switchUsePreview.setOnCheckedChangeListener(((buttonView, isChecked) -> APP.putBoolean(USE_PREVIEW, isChecked))); + binding.switchUsePreview.setOnCheckedChangeListener(((buttonView, isChecked) -> { + showStyleView(isChecked); + APP.putBoolean(USE_PREVIEW, isChecked); + })); TtsActorAdapter actorAdapter = new TtsActorAdapter(TtsActorManger.getInstance().getActors()); @@ -186,6 +190,17 @@ private void connectToText2Speech() { } + private void showStyleView(boolean show) { + if (show) { + binding.rvVoiceStyles.setVisibility(View.VISIBLE); + binding.ttsStyleDegreeParent.setVisibility(View.VISIBLE); + } else { + binding.rvVoiceStyles.setVisibility(View.GONE); + binding.ttsStyleDegreeParent.setVisibility(View.GONE); + } + } + + @SuppressLint("SetTextI18n") private void updateView() { APP.putInt(Constants.VOICE_STYLE_DEGREE, styleDegree); diff --git a/app/src/main/java/me/ag2s/tts/services/Constants.java b/app/src/main/java/me/ag2s/tts/services/Constants.java index 7a192b19..78f24dfb 100644 --- a/app/src/main/java/me/ag2s/tts/services/Constants.java +++ b/app/src/main/java/me/ag2s/tts/services/Constants.java @@ -57,7 +57,7 @@ public final class Constants { public static final String EDGE_ORIGIN = "chrome-extension://jdiccldimpdaibmpdkjnbmckianbfold"; - public static final String EDGE_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.47"; + public static final String EDGE_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.124 Safari/537.36 Edg/102.0.1245.41"; public static final String EDGE_URL = "https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1?TrustedClientToken=6A5AA1D4EAFF4E9FB37E23D68491D6F4"; } diff --git a/app/src/main/java/me/ag2s/tts/services/SSML.java b/app/src/main/java/me/ag2s/tts/services/SSML.java index 7173c277..10048867 100644 --- a/app/src/main/java/me/ag2s/tts/services/SSML.java +++ b/app/src/main/java/me/ag2s/tts/services/SSML.java @@ -73,13 +73,16 @@ public class SSML { private short pitch; private short rate; private boolean useDict; + //是否使用预览版 + private boolean usePre; private static SSML instance; - private SSML(SynthesisRequest request, String name, TtsStyle ttsStyle, boolean useDict) { + private SSML(SynthesisRequest request, String name, TtsStyle ttsStyle, boolean useDict, boolean usePre) { this.content = new StringBuilder(request.getCharSequenceText()); this.useDict = useDict; + this.usePre = usePre; this.name = name; this.style = new WeakReference<>(ttsStyle); this.time = CommonTool.getTime(); @@ -97,19 +100,19 @@ private SSML(SynthesisRequest request, String name, TtsStyle ttsStyle, boolean u } - public static SSML getInstance(SynthesisRequest request, String name, TtsStyle ttsStyle, boolean useDict) { + public static SSML getInstance(SynthesisRequest request, String name, TtsStyle ttsStyle, boolean useDict, boolean usePre) { if (instance == null) { - instance = new SSML(request, name, ttsStyle, useDict); + instance = new SSML(request, name, ttsStyle, useDict, usePre); } else { - instance.content=new StringBuilder(request.getCharSequenceText()); + instance.content = new StringBuilder(request.getCharSequenceText()); instance.useDict = useDict; + instance.usePre = usePre; instance.name = name; instance.style = new WeakReference<>(ttsStyle); instance.time = CommonTool.getTime(); instance.pitch = (short) (request.getPitch() - 100); instance.rate = (short) (request.getSpeechRate()); instance.id = CommonTool.getMD5String(request.getCharSequenceText() + "" + System.currentTimeMillis()); - instance.useDict = useDict; instance.handleContent(); } return instance; @@ -128,7 +131,7 @@ private void handleContent() { CommonTool.replace(content, ">", "<"); CommonTool.replace(content, "<", ">"); //是否分段 - if (APP.getBoolean(Constants.SPLIT_SENTENCE, false)) { + if (APP.getBoolean(Constants.SPLIT_SENTENCE, false) && usePre) { String temp = content.toString(); temp = p0.matcher(temp).replaceAll("$1");//把常用的影响分句的重复符号合并 temp = p1.matcher(temp).replaceAll("$1

$2");//单字符断句符,排除后面有引号的情况 @@ -155,20 +158,45 @@ private void handleContent() { @NonNull @Override public String toString() { - String rateString =rate/100+"."+rate%100; - String pitchString = pitch >= 0 ? "+" + pitch + "Hz" : pitch + "Hz"; - return "X-RequestId:" + id + "\r\n" + - "Content-Type:application/ssml+xml\r\n" + - "X-Timestamp:" + time + "Z\r\n" + - - "Path:ssml\r\n\r\n" + - "" + - "" + - "" + - "" + - "

" + content.toString() + "

" + - ""; + String rateString = rate / 100 + "." + rate % 100; +// if (!usePre) { +// return "Path: ssml" + "\r\n" + +// "X-RequestId: " + id + "\r\n" + +// "X-Timestamp: " + time + "Z" + "\r\n" + +// "Content-Type: application/ssml+xml" + "\r\n\r\n" + +// "" + content.toString() + "\r\n" + +// ""; +// } + //String pitchString = pitch >= 0 ? "+" + pitch + "Hz" : pitch + "Hz"; + StringBuilder sb = new StringBuilder() + .append("Path:ssml\r\n") + .append("X-RequestId:").append(id).append("\r\n") + .append("X-Timestamp:").append(time).append("Z\r\n") + .append("Content-Type:application/ssml+xml\r\n\r\n"); + + + sb.append(""); + sb.append(""); + if (usePre) { + sb.append(""); + } + sb.append(""); + + if (usePre) { + sb.append("

").append(content.toString()).append("

"); + } else { + sb.append("").append(content.toString()).append(""); + + } + + + sb.append("
"); + if (usePre) { + sb.append("
"); + } + + sb.append("
"); + + return sb.toString(); } } diff --git a/app/src/main/java/me/ag2s/tts/services/TTSService.java b/app/src/main/java/me/ag2s/tts/services/TTSService.java index 74e0ea52..6d28d94d 100644 --- a/app/src/main/java/me/ag2s/tts/services/TTSService.java +++ b/app/src/main/java/me/ag2s/tts/services/TTSService.java @@ -68,6 +68,7 @@ public class TTSService extends TextToSpeechService { private final OkHttpClient client; @Nullable private volatile WebSocket webSocket; + private volatile boolean isPreview = false; private volatile boolean isSynthesizing = false; //当前的生成格式 private volatile TtsOutputFormat currentFormat; @@ -564,18 +565,29 @@ public WebSocket getOrCreateWs() { synchronized (TTSService.class) { if (this.webSocket == null) { + while (TokenHolder.token == null) { + try { + this.wait(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + String url; - String origin = Constants.EDGE_ORIGIN; + String origin; if (TokenHolder.token != null && APP.getBoolean(Constants.USE_PREVIEW, false)) { url = "wss://eastus.tts.speech.microsoft.com/cognitiveservices/websocket/v1?Authorization=bearer " + TokenHolder.token + "&X-ConnectionId=" + CommonTool.getMD5String(new Date().toString()); origin = "https://azure.microsoft.com"; + isPreview = true; } else { - url = Constants.EDGE_URL + "&ConnectionId=" + CommonTool.getMD5String(new Date().toString()); + url = Constants.EDGE_URL; + isPreview = false; + origin = Constants.EDGE_ORIGIN; } Request request = new Request.Builder() .url(url) - .header("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6") - .header("Accept-Encoding", "gzip, deflate") + //.header("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6") + //.header("Accept-Encoding", "gzip, deflate") .header("User-Agent", Constants.EDGE_UA) .addHeader("Origin", origin) .build(); @@ -595,7 +607,7 @@ public WebSocket getOrCreateWs() { * 发送合成语音配置,更改格式需要重新发送 */ private synchronized void sendConfig(WebSocket ws, TtsConfig ttsConfig) { - String msg = "X-Timestamp:+" + getTime() + "Z\r\n" + + String msg = "X-Timestamp:+" + getTime() + "\r\n" + "Content-Type:application/json; charset=utf-8\r\n" + "Path:speech.config\r\n\r\n" + ttsConfig.toString(); @@ -637,14 +649,15 @@ public synchronized void sendText(SynthesisRequest request, SynthesisCallback ca ttsStyle.setStyleDegree(APP.getInt(Constants.VOICE_STYLE_DEGREE, 100)); ttsStyle.setVolume(APP.getInt(Constants.VOICE_VOLUME, 100)); boolean useDict = APP.getBoolean(Constants.USE_DICT, false); - SSML ssml = SSML.getInstance(request, name, ttsStyle, useDict); - Log.e(TAG, ssml.toString()); + //webSocket = webSocket == null ? getOrCreateWs() : webSocket; if (oldFormatIndex != index) { sendConfig(getOrCreateWs(), ttsConfig); oldFormatIndex = index; } + SSML ssml = SSML.getInstance(request, name, ttsStyle, useDict, isPreview); + Log.e(TAG, ssml.toString()); //在Google Play图书之类应用会闪退,应该及时调用该方法 callback.start(currentFormat.HZ, currentFormat.BitRate, 1 /* Number of channels. */); diff --git a/app/src/main/java/me/ag2s/tts/services/TtsActor.java b/app/src/main/java/me/ag2s/tts/services/TtsActor.java index a027d7a4..7583671f 100644 --- a/app/src/main/java/me/ag2s/tts/services/TtsActor.java +++ b/app/src/main/java/me/ag2s/tts/services/TtsActor.java @@ -32,6 +32,7 @@ public class TtsActor { */ @Nullable private String note; + private Locale tempLocate; public TtsActor(@NonNull String name, @NonNull String shortName, @NonNull String locate, boolean gender, @Nullable String note) { this.name = name; @@ -53,10 +54,11 @@ public TtsActor(String shortName, boolean gender, @Nullable String note) { } //String[] temp = locale.split(tag); - this.name = shortName.substring(shortName.lastIndexOf(tag) + 1).replace("Neural",""); + this.name = shortName.substring(shortName.lastIndexOf(tag) + 1).replace("Neural", ""); this.locale = shortName.substring(0, shortName.lastIndexOf(tag)); } + @SuppressWarnings("unused") public TtsActor(String name, String shortName, String locate, boolean gender) { this(name, shortName, locate, gender, ""); @@ -92,6 +94,11 @@ public void setGender(boolean gender) { } public Locale getLocale() { + if (tempLocate != null) { + return tempLocate; + } + + String tag = "-"; if (locale.contains("-")) { tag = "-"; @@ -118,3 +125,5 @@ public void setNote(@Nullable String note) { this.note = note; } } + + diff --git a/app/src/main/java/me/ag2s/tts/services/TtsActorManger.java b/app/src/main/java/me/ag2s/tts/services/TtsActorManger.java index 4fd2b6ea..68f8498e 100644 --- a/app/src/main/java/me/ag2s/tts/services/TtsActorManger.java +++ b/app/src/main/java/me/ag2s/tts/services/TtsActorManger.java @@ -1,5 +1,6 @@ package me.ag2s.tts.services; +import android.os.Build; import android.util.Log; import androidx.annotation.NonNull; @@ -508,48 +509,50 @@ private TtsActorManger() { } public List sortByLocale(List list, Locale locale) { - Collections.sort(list, (o1, o2) -> { - Locale loc1 = o1.getLocale(); - Locale loc2 = o2.getLocale(); - boolean b11 = loc1.getISO3Language().equals(locale.getISO3Language()); - boolean b12 = loc1.getISO3Country().equals(locale.getISO3Country()); - boolean b13 = loc1.getDisplayVariant(Locale.US).equals(locale.getDisplayVariant(Locale.US)); - boolean b21 = loc2.getISO3Language().equals(locale.getISO3Language()); - boolean b22 = loc2.getISO3Country().equals(locale.getISO3Country()); - boolean b23 = loc2.getDisplayVariant(Locale.US).equals(locale.getDisplayVariant(Locale.US)); - //语言不同 - if ((!b11) && (!b21)) { - return 0; - } - //两个都相同 - if (b11 && b12 && b13 == b21 && b22 && b23) { - return 0; - } - if (b11 && b12 && b13) { - return -1; - } - if (b21 && b22 && b23) { - return 1; - } - - if ((b11 && b12 == b21 && b22)) { - if (b13 == b23) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Collections.sort(list, (o1, o2) -> { + Locale loc1 = o1.getLocale(); + Locale loc2 = o2.getLocale(); + boolean b11 = loc1.getISO3Language().equals(locale.getISO3Language()); + boolean b12 = loc1.getISO3Country().equals(locale.getISO3Country()); + boolean b13 = loc1.getDisplayVariant(Locale.US).equals(locale.getDisplayVariant(Locale.US)); + boolean b21 = loc2.getISO3Language().equals(locale.getISO3Language()); + boolean b22 = loc2.getISO3Country().equals(locale.getISO3Country()); + boolean b23 = loc2.getDisplayVariant(Locale.US).equals(locale.getDisplayVariant(Locale.US)); + //语言都不同 + if ((!b11) && (!b21)) { + return 0; + } + //两个都相同 + if (b11 && b12 && b13 == b21 && b22 && b23) { return 0; } - if (b13) { + if (b11 && b12 && b13) { return -1; - } else { + } + if (b21 && b22 && b23) { return 1; } - } - if (b11 && b12) { - return -1; - } - if (b21 && b22) { - return 1; - } - return 0; - }); + + if ((b11 && b12 == b21 && b22)) { + if (b13 == b23) { + return 0; + } + if (b13) { + return -1; + } else { + return 1; + } + } + if (b11 && b12) { + return -1; + } + if (b21 && b22) { + return 1; + } + return 0; + }); + } return list; } @@ -571,6 +574,7 @@ public TtsActor getByName(@NonNull String name) { */ @SuppressWarnings("unused") public synchronized List getActors() { + return sortByLocale(this.actors, Locale.getDefault()); //return this.actors; } diff --git a/app/src/main/java/me/ag2s/tts/services/TtsConfig.java b/app/src/main/java/me/ag2s/tts/services/TtsConfig.java index 01832994..fe9b940c 100644 --- a/app/src/main/java/me/ag2s/tts/services/TtsConfig.java +++ b/app/src/main/java/me/ag2s/tts/services/TtsConfig.java @@ -31,6 +31,12 @@ public TtsOutputFormat getFormat() { @NotNull @Override public String toString() { + //X-Timestamp:Thu Jun 16 2022 19:13:55 GMT+0800 (中国标准时间) + //Content-Type:application/json; charset=utf-8 + //Path:speech.config + // + //{"context":{"synthesis":{"audio":{"metadataoptions":{"sentenceBoundaryEnabled":"false","wordBoundaryEnabled":"true"},"outputFormat":"webm-24khz-16bit-mono-opus"}}}} + String msg = "{\"context\":{\"synthesis\":{\"audio\":{\"metadataoptions\":{\"sentenceBoundaryEnabled\":\"%s\",\"wordBoundaryEnabled\":\"%s\"},\"outputFormat\":\"%s\"}}}}"; msg = String.format(msg, sentenceBoundaryEnabled ? "true" : "false", "true", TtsFormatManger.getInstance().getFormat(index).value); Log.d(TTSService.class.getSimpleName(), msg); diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 549c8788..8faf1afa 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -22,6 +22,13 @@ android:layout_height="wrap_content" android:text="@string/set_tts" /> + +