mirror of
https://github.com/jahruz67/Bible-Daily.git
synced 2026-08-08 18:14:05 +00:00
Merge pull request #1 from jahruz67/refactor/mistral-tts-support
Add Mistral API TTS support with provider selection
This commit is contained in:
@@ -86,8 +86,9 @@ public class MainActivity extends Activity {
|
|||||||
private static final int RANK_FEAST = 2;
|
private static final int RANK_FEAST = 2;
|
||||||
private static final int RANK_SOLEMNITY = 3;
|
private static final int RANK_SOLEMNITY = 3;
|
||||||
|
|
||||||
private static final String TTS_SPEECH_URL = "https://ttsapi.host2.gleeze.com/v1/audio/speech";
|
private static final String TTS_SPEECH_URL_GLEEZE = "https://ttsapi.host2.gleeze.com/v1/audio/speech";
|
||||||
private static final String TTS_MODEL = "kokoro";
|
private static final String TTS_MODEL_GLEEZE = "kokoro";
|
||||||
|
private static final String TTS_SPEECH_URL_MISTRAL = "https://api.mistral.ai/v1/text-to-speech";
|
||||||
private static final String READING_CACHE_DIR = "reading-cache";
|
private static final String READING_CACHE_DIR = "reading-cache";
|
||||||
private static final String PREFETCH_MONTH_PREFIX = "prefetch_month_";
|
private static final String PREFETCH_MONTH_PREFIX = "prefetch_month_";
|
||||||
private static final int NEXT_MONTH_PREFETCH_DAY = 26;
|
private static final int NEXT_MONTH_PREFETCH_DAY = 26;
|
||||||
@@ -1490,7 +1491,16 @@ public class MainActivity extends Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private File downloadTtsAudio(String text) throws Exception {
|
private File downloadTtsAudio(String text) throws Exception {
|
||||||
HttpURLConnection connection = (HttpURLConnection) new URL(TTS_SPEECH_URL).openConnection();
|
String provider = UpdateManager.getTtsProvider(this);
|
||||||
|
if (provider.equals(UpdateManager.TTS_PROVIDER_MISTRAL)) {
|
||||||
|
return downloadMistralTtsAudio(text);
|
||||||
|
} else {
|
||||||
|
return downloadGleezTtsAudio(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private File downloadGleezTtsAudio(String text) throws Exception {
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) new URL(TTS_SPEECH_URL_GLEEZE).openConnection();
|
||||||
connection.setConnectTimeout(15000);
|
connection.setConnectTimeout(15000);
|
||||||
connection.setReadTimeout(120000);
|
connection.setReadTimeout(120000);
|
||||||
connection.setRequestMethod("POST");
|
connection.setRequestMethod("POST");
|
||||||
@@ -1500,8 +1510,8 @@ public class MainActivity extends Activity {
|
|||||||
connection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
|
connection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
|
||||||
|
|
||||||
JSONObject body = new JSONObject();
|
JSONObject body = new JSONObject();
|
||||||
body.put("model", TTS_MODEL);
|
body.put("model", TTS_MODEL_GLEEZE);
|
||||||
body.put("voice", UpdateManager.getTtsVoice(this, isEnglish));
|
body.put("voice", UpdateManager.getTtsVoice(this, isEnglish, UpdateManager.TTS_PROVIDER_GLEEZE));
|
||||||
body.put("input", text);
|
body.put("input", text);
|
||||||
body.put("response_format", "mp3");
|
body.put("response_format", "mp3");
|
||||||
body.put("speed", 1.0);
|
body.put("speed", 1.0);
|
||||||
@@ -1539,6 +1549,59 @@ public class MainActivity extends Activity {
|
|||||||
return outputFile;
|
return outputFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private File downloadMistralTtsAudio(String text) throws Exception {
|
||||||
|
String apiKey = UpdateManager.getMistralApiKey(this);
|
||||||
|
if (apiKey == null || apiKey.trim().isEmpty()) {
|
||||||
|
throw new IOException(isEnglish ? "Mistral API key is not configured. Please set it in Settings." : "La clave API de Mistral no está configurada. Configúrela en Ajustes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) new URL(TTS_SPEECH_URL_MISTRAL).openConnection();
|
||||||
|
connection.setConnectTimeout(15000);
|
||||||
|
connection.setReadTimeout(120000);
|
||||||
|
connection.setRequestMethod("POST");
|
||||||
|
connection.setDoOutput(true);
|
||||||
|
connection.setRequestProperty("User-Agent", "BibliaDiaria/1.0 Android");
|
||||||
|
connection.setRequestProperty("Accept", "audio/mpeg");
|
||||||
|
connection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
|
||||||
|
connection.setRequestProperty("Authorization", "Bearer " + apiKey);
|
||||||
|
|
||||||
|
JSONObject body = new JSONObject();
|
||||||
|
body.put("text", text);
|
||||||
|
body.put("voice", UpdateManager.getTtsVoice(this, isEnglish, UpdateManager.TTS_PROVIDER_MISTRAL));
|
||||||
|
|
||||||
|
byte[] payload = body.toString().getBytes(StandardCharsets.UTF_8);
|
||||||
|
connection.setFixedLengthStreamingMode(payload.length);
|
||||||
|
try (OutputStream stream = connection.getOutputStream()) {
|
||||||
|
stream.write(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
int responseCode = connection.getResponseCode();
|
||||||
|
if (responseCode < 200 || responseCode >= 300) {
|
||||||
|
String details = "";
|
||||||
|
InputStream errorStream = connection.getErrorStream();
|
||||||
|
if (errorStream != null) {
|
||||||
|
details = readStream(errorStream);
|
||||||
|
}
|
||||||
|
connection.disconnect();
|
||||||
|
throw new IOException((isEnglish ? "Mistral TTS API responded with code " : "La API TTS de Mistral respondió con código ")
|
||||||
|
+ responseCode
|
||||||
|
+ (details.isEmpty() ? "." : ". " + details));
|
||||||
|
}
|
||||||
|
|
||||||
|
File outputFile = File.createTempFile("daily-reading-tts-", ".mp3", getCacheDir());
|
||||||
|
try (InputStream input = connection.getInputStream();
|
||||||
|
FileOutputStream output = new FileOutputStream(outputFile)) {
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int read;
|
||||||
|
while ((read = input.read(buffer)) != -1) {
|
||||||
|
output.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
connection.disconnect();
|
||||||
|
}
|
||||||
|
return outputFile;
|
||||||
|
}
|
||||||
|
|
||||||
private void showTtsAudioReady(File audioFile) {
|
private void showTtsAudioReady(File audioFile) {
|
||||||
try {
|
try {
|
||||||
releaseTtsPlayer();
|
releaseTtsPlayer();
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ import android.graphics.Typeface;
|
|||||||
import android.graphics.drawable.GradientDrawable;
|
import android.graphics.drawable.GradientDrawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.InputType;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.EditText;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ScrollView;
|
import android.widget.ScrollView;
|
||||||
@@ -45,6 +47,8 @@ public class SettingsActivity extends Activity {
|
|||||||
private TextView downloadButton;
|
private TextView downloadButton;
|
||||||
private TextView voiceSubtitleText;
|
private TextView voiceSubtitleText;
|
||||||
private TextView voiceButton;
|
private TextView voiceButton;
|
||||||
|
private TextView ttsProviderButton;
|
||||||
|
private TextView mistralApiKeyButton;
|
||||||
private UpdateManager.UpdateInfo latestInfo;
|
private UpdateManager.UpdateInfo latestInfo;
|
||||||
private boolean isEnglish;
|
private boolean isEnglish;
|
||||||
|
|
||||||
@@ -100,7 +104,9 @@ public class SettingsActivity extends Activity {
|
|||||||
content.addView(title, titleParams);
|
content.addView(title, titleParams);
|
||||||
|
|
||||||
content.addView(createLanguageCard());
|
content.addView(createLanguageCard());
|
||||||
|
content.addView(createTtsProviderCard());
|
||||||
content.addView(createVoiceCard());
|
content.addView(createVoiceCard());
|
||||||
|
content.addView(createMistralApiKeyCard());
|
||||||
content.addView(createVersionCard());
|
content.addView(createVersionCard());
|
||||||
|
|
||||||
checkButton = actionButton(isEnglish ? "Check now" : "Comprobar", COLOR_ACCENT, Color.WHITE);
|
checkButton = actionButton(isEnglish ? "Check now" : "Comprobar", COLOR_ACCENT, Color.WHITE);
|
||||||
@@ -200,6 +206,8 @@ public class SettingsActivity extends Activity {
|
|||||||
boolean current = UpdateManager.isEnglish(this);
|
boolean current = UpdateManager.isEnglish(this);
|
||||||
UpdateManager.setEnglish(this, !current);
|
UpdateManager.setEnglish(this, !current);
|
||||||
langToggle.setText(!current ? "English" : "Español");
|
langToggle.setText(!current ? "English" : "Español");
|
||||||
|
isEnglish = !current;
|
||||||
|
recreate();
|
||||||
updateVoiceCard();
|
updateVoiceCard();
|
||||||
});
|
});
|
||||||
card.addView(langToggle, new LinearLayout.LayoutParams(dp(100), dp(40)));
|
card.addView(langToggle, new LinearLayout.LayoutParams(dp(100), dp(40)));
|
||||||
@@ -207,6 +215,39 @@ public class SettingsActivity extends Activity {
|
|||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private View createTtsProviderCard() {
|
||||||
|
LinearLayout card = createCard();
|
||||||
|
card.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
card.setGravity(Gravity.CENTER_VERTICAL);
|
||||||
|
|
||||||
|
LinearLayout copy = new LinearLayout(this);
|
||||||
|
copy.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
|
||||||
|
TextView title = textView("TTS Provider", 20, ink(), Typeface.BOLD);
|
||||||
|
title.setIncludeFontPadding(false);
|
||||||
|
copy.addView(title);
|
||||||
|
|
||||||
|
TextView subtitle = textView("Choose between Gleez and Mistral TTS", 14, muted(), Typeface.NORMAL);
|
||||||
|
LinearLayout.LayoutParams subtitleParams = new LinearLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
);
|
||||||
|
subtitleParams.setMargins(0, dp(6), 0, 0);
|
||||||
|
copy.addView(subtitle, subtitleParams);
|
||||||
|
|
||||||
|
card.addView(copy, new LinearLayout.LayoutParams(
|
||||||
|
0,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
1f
|
||||||
|
));
|
||||||
|
|
||||||
|
ttsProviderButton = actionButton(getTtsProviderLabel(), COLOR_ACCENT, Color.WHITE);
|
||||||
|
ttsProviderButton.setOnClickListener(view -> showTtsProviderPicker());
|
||||||
|
card.addView(ttsProviderButton, new LinearLayout.LayoutParams(dp(140), dp(40)));
|
||||||
|
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
private View createVoiceCard() {
|
private View createVoiceCard() {
|
||||||
LinearLayout card = createCard();
|
LinearLayout card = createCard();
|
||||||
card.setOrientation(LinearLayout.VERTICAL);
|
card.setOrientation(LinearLayout.VERTICAL);
|
||||||
@@ -244,6 +285,42 @@ public class SettingsActivity extends Activity {
|
|||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private View createMistralApiKeyCard() {
|
||||||
|
LinearLayout card = createCard();
|
||||||
|
card.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
|
||||||
|
LinearLayout copy = new LinearLayout(this);
|
||||||
|
copy.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
|
||||||
|
TextView title = textView("Mistral API Key", 20, ink(), Typeface.BOLD);
|
||||||
|
title.setIncludeFontPadding(false);
|
||||||
|
copy.addView(title);
|
||||||
|
|
||||||
|
TextView subtitle = textView("Required for Mistral TTS (get from mistral.ai)", 14, muted(), Typeface.NORMAL);
|
||||||
|
LinearLayout.LayoutParams subtitleParams = new LinearLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
);
|
||||||
|
subtitleParams.setMargins(0, dp(6), 0, 0);
|
||||||
|
copy.addView(subtitle, subtitleParams);
|
||||||
|
|
||||||
|
card.addView(copy, new LinearLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
));
|
||||||
|
|
||||||
|
mistralApiKeyButton = actionButton(getMistralApiKeyLabel(), COLOR_ACCENT, Color.WHITE);
|
||||||
|
mistralApiKeyButton.setOnClickListener(view -> showMistralApiKeyDialog());
|
||||||
|
LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
dp(42)
|
||||||
|
);
|
||||||
|
buttonParams.setMargins(0, dp(12), 0, 0);
|
||||||
|
card.addView(mistralApiKeyButton, buttonParams);
|
||||||
|
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
private View createVersionCard() {
|
private View createVersionCard() {
|
||||||
LinearLayout card = createCard();
|
LinearLayout card = createCard();
|
||||||
card.setOrientation(LinearLayout.VERTICAL);
|
card.setOrientation(LinearLayout.VERTICAL);
|
||||||
@@ -319,7 +396,8 @@ public class SettingsActivity extends Activity {
|
|||||||
|
|
||||||
private void showVoicePicker() {
|
private void showVoicePicker() {
|
||||||
boolean english = UpdateManager.isEnglish(this);
|
boolean english = UpdateManager.isEnglish(this);
|
||||||
UpdateManager.VoiceOption[] options = UpdateManager.getTtsVoiceOptions(english);
|
String provider = UpdateManager.getTtsProvider(this);
|
||||||
|
UpdateManager.VoiceOption[] options = UpdateManager.getTtsVoiceOptions(english, provider);
|
||||||
String[] labels = new String[options.length];
|
String[] labels = new String[options.length];
|
||||||
for (int index = 0; index < options.length; index++) {
|
for (int index = 0; index < options.length; index++) {
|
||||||
labels[index] = options[index].label;
|
labels[index] = options[index].label;
|
||||||
@@ -327,13 +405,14 @@ public class SettingsActivity extends Activity {
|
|||||||
|
|
||||||
int selectedIndex = UpdateManager.getTtsVoiceIndex(
|
int selectedIndex = UpdateManager.getTtsVoiceIndex(
|
||||||
english,
|
english,
|
||||||
UpdateManager.getTtsVoice(this, english)
|
provider,
|
||||||
|
UpdateManager.getTtsVoice(this, english, provider)
|
||||||
);
|
);
|
||||||
|
|
||||||
new AlertDialog.Builder(this)
|
new AlertDialog.Builder(this)
|
||||||
.setTitle(english ? "English voice" : "Spanish voice")
|
.setTitle(english ? "English voice" : "Spanish voice")
|
||||||
.setSingleChoiceItems(labels, selectedIndex, (dialog, which) -> {
|
.setSingleChoiceItems(labels, selectedIndex, (dialog, which) -> {
|
||||||
UpdateManager.setTtsVoice(this, english, options[which].id);
|
UpdateManager.setTtsVoice(this, english, provider, options[which].id);
|
||||||
updateVoiceCard();
|
updateVoiceCard();
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
})
|
})
|
||||||
@@ -341,17 +420,94 @@ public class SettingsActivity extends Activity {
|
|||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showTtsProviderPicker() {
|
||||||
|
String[] providers = {"Gleez", "Mistral"};
|
||||||
|
String currentProvider = UpdateManager.getTtsProvider(this);
|
||||||
|
int selectedIndex = currentProvider.equals(UpdateManager.TTS_PROVIDER_MISTRAL) ? 1 : 0;
|
||||||
|
|
||||||
|
new AlertDialog.Builder(this)
|
||||||
|
.setTitle("Select TTS Provider")
|
||||||
|
.setSingleChoiceItems(providers, selectedIndex, (dialog, which) -> {
|
||||||
|
String provider = which == 1 ? UpdateManager.TTS_PROVIDER_MISTRAL : UpdateManager.TTS_PROVIDER_GLEEZE;
|
||||||
|
UpdateManager.setTtsProvider(this, provider);
|
||||||
|
ttsProviderButton.setText(getTtsProviderLabel());
|
||||||
|
updateVoiceCard();
|
||||||
|
dialog.dismiss();
|
||||||
|
})
|
||||||
|
.setNegativeButton("Cancel", null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showMistralApiKeyDialog() {
|
||||||
|
String currentKey = UpdateManager.getMistralApiKey(this);
|
||||||
|
String displayKey = currentKey.isEmpty() ? "" : "••••••••";
|
||||||
|
|
||||||
|
EditText input = new EditText(this);
|
||||||
|
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||||
|
input.setHint("Enter your Mistral API key");
|
||||||
|
input.setText(currentKey);
|
||||||
|
input.setSelection(currentKey.length());
|
||||||
|
|
||||||
|
LinearLayout layout = new LinearLayout(this);
|
||||||
|
layout.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
layout.setPadding(dp(20), dp(20), dp(20), dp(20));
|
||||||
|
layout.addView(input, new LinearLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
));
|
||||||
|
|
||||||
|
new AlertDialog.Builder(this)
|
||||||
|
.setTitle("Mistral API Key")
|
||||||
|
.setView(layout)
|
||||||
|
.setPositiveButton("Save", (dialog, which) -> {
|
||||||
|
UpdateManager.setMistralApiKey(this, input.getText().toString().trim());
|
||||||
|
mistralApiKeyButton.setText(getMistralApiKeyLabel());
|
||||||
|
})
|
||||||
|
.setNegativeButton("Cancel", null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTtsProviderLabel() {
|
||||||
|
String provider = UpdateManager.getTtsProvider(this);
|
||||||
|
return provider.equals(UpdateManager.TTS_PROVIDER_MISTRAL) ? "Mistral" : "Gleez";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getMistralApiKeyLabel() {
|
||||||
|
String apiKey = UpdateManager.getMistralApiKey(this);
|
||||||
|
if (apiKey.isEmpty()) {
|
||||||
|
return "Not configured";
|
||||||
|
}
|
||||||
|
return "••••••••";
|
||||||
|
}
|
||||||
|
|
||||||
private void updateVoiceCard() {
|
private void updateVoiceCard() {
|
||||||
if (voiceSubtitleText == null || voiceButton == null) {
|
if (voiceSubtitleText == null || voiceButton == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean english = UpdateManager.isEnglish(this);
|
boolean english = UpdateManager.isEnglish(this);
|
||||||
|
String provider = UpdateManager.getTtsProvider(this);
|
||||||
String voiceId = UpdateManager.getTtsVoice(this, english);
|
String voiceId = UpdateManager.getTtsVoice(this, english);
|
||||||
|
|
||||||
|
// Update subtitle based on provider
|
||||||
|
if (provider.equals(UpdateManager.TTS_PROVIDER_MISTRAL)) {
|
||||||
voiceSubtitleText.setText(english
|
voiceSubtitleText.setText(english
|
||||||
? "English voices are shown while English is selected"
|
? "Mistral voices (requires API key)"
|
||||||
: "Las voces en español se muestran mientras el español esté seleccionado");
|
: "Voces de Mistral (requiere clave API)");
|
||||||
voiceButton.setText(UpdateManager.getTtsVoiceLabel(english, voiceId));
|
} else {
|
||||||
|
voiceSubtitleText.setText(english
|
||||||
|
? "Gleez voices are shown while English is selected"
|
||||||
|
: "Las voces de Gleez se muestran mientras el idioma esta seleccionado");
|
||||||
|
}
|
||||||
|
|
||||||
|
voiceButton.setText(UpdateManager.getTtsVoiceLabel(english, provider, voiceId));
|
||||||
|
|
||||||
|
// If Mistral is selected but API key is not configured, show a warning
|
||||||
|
if (provider.equals(UpdateManager.TTS_PROVIDER_MISTRAL) && !UpdateManager.isMistralConfigured(this)) {
|
||||||
|
voiceSubtitleText.setText(english
|
||||||
|
? "Mistral voices (requires API key - not configured!)"
|
||||||
|
: "Voces de Mistral (requiere clave API - ¡no configurada!)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateInstalledVersionText() {
|
private void updateInstalledVersionText() {
|
||||||
|
|||||||
@@ -29,12 +29,19 @@ final class UpdateManager {
|
|||||||
static final String KEY_TTS_VOICE_SPANISH = "tts_voice_spanish";
|
static final String KEY_TTS_VOICE_SPANISH = "tts_voice_spanish";
|
||||||
static final String DEFAULT_TTS_VOICE_ENGLISH = "af_heart";
|
static final String DEFAULT_TTS_VOICE_ENGLISH = "af_heart";
|
||||||
static final String DEFAULT_TTS_VOICE_SPANISH = "ef_dora";
|
static final String DEFAULT_TTS_VOICE_SPANISH = "ef_dora";
|
||||||
|
static final String DEFAULT_TTS_VOICE_ENGLISH_MISTRAL = "v2/en_us-florence";
|
||||||
|
static final String DEFAULT_TTS_VOICE_SPANISH_MISTRAL = "v2/es_es-carlota";
|
||||||
|
static final String KEY_TTS_PROVIDER = "tts_provider";
|
||||||
|
static final String KEY_MISTRAL_API_KEY = "mistral_api_key";
|
||||||
|
static final String TTS_PROVIDER_GLEEZE = "gleeze";
|
||||||
|
static final String TTS_PROVIDER_MISTRAL = "mistral";
|
||||||
|
static final String MISTRAL_TTS_URL = "https://api.mistral.ai/v1/text-to-speech";
|
||||||
static final String LATEST_JSON_URL =
|
static final String LATEST_JSON_URL =
|
||||||
"https://github.com/jahruz67/Bible-Daily/releases/download/latest/latest.json";
|
"https://github.com/jahruz67/Bible-Daily/releases/download/latest/latest.json";
|
||||||
static final String APK_URL =
|
static final String APK_URL =
|
||||||
"https://github.com/jahruz67/Bible-Daily/releases/download/latest/app-debug.apk";
|
"https://github.com/jahruz67/Bible-Daily/releases/download/latest/app-debug.apk";
|
||||||
|
|
||||||
private static final VoiceOption[] ENGLISH_VOICES = {
|
private static final VoiceOption[] ENGLISH_VOICES_GLEEZE = {
|
||||||
new VoiceOption("af_heart", "Heart (US female)"),
|
new VoiceOption("af_heart", "Heart (US female)"),
|
||||||
new VoiceOption("af_alloy", "Alloy (US female)"),
|
new VoiceOption("af_alloy", "Alloy (US female)"),
|
||||||
new VoiceOption("af_aoede", "Aoede (US female)"),
|
new VoiceOption("af_aoede", "Aoede (US female)"),
|
||||||
@@ -65,12 +72,27 @@ final class UpdateManager {
|
|||||||
new VoiceOption("bm_lewis", "Lewis (UK male)")
|
new VoiceOption("bm_lewis", "Lewis (UK male)")
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final VoiceOption[] SPANISH_VOICES = {
|
private static final VoiceOption[] ENGLISH_VOICES_MISTRAL = {
|
||||||
|
new VoiceOption("v2/en_us-florence", "Florence (US female)"),
|
||||||
|
new VoiceOption("v2/en_us-dave", "Dave (US male)"),
|
||||||
|
new VoiceOption("v2/en_us-libby", "Libby (US female)"),
|
||||||
|
new VoiceOption("v2/en_us-matt", "Matt (US male)"),
|
||||||
|
new VoiceOption("v2/en_us-serena", "Serena (US female)"),
|
||||||
|
new VoiceOption("v2/en_us-andrew", "Andrew (US male)")
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final VoiceOption[] SPANISH_VOICES_GLEEZE = {
|
||||||
new VoiceOption("ef_dora", "Dora (Spanish female)"),
|
new VoiceOption("ef_dora", "Dora (Spanish female)"),
|
||||||
new VoiceOption("em_alex", "Alex (Spanish male)"),
|
new VoiceOption("em_alex", "Alex (Spanish male)"),
|
||||||
new VoiceOption("em_santa", "Santa (Spanish male)")
|
new VoiceOption("em_santa", "Santa (Spanish male)")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static final VoiceOption[] SPANISH_VOICES_MISTRAL = {
|
||||||
|
new VoiceOption("v2/es_es-alvaro", "Alvaro (Spanish male)"),
|
||||||
|
new VoiceOption("v2/es_es-carlota", "Carlota (Spanish female)"),
|
||||||
|
new VoiceOption("v2/es_es-gerardo", "Gerardo (Spanish male)")
|
||||||
|
};
|
||||||
|
|
||||||
private UpdateManager() {
|
private UpdateManager() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,35 +120,76 @@ final class UpdateManager {
|
|||||||
preferences(context).edit().putBoolean(KEY_DARK_MODE, enabled).apply();
|
preferences(context).edit().putBoolean(KEY_DARK_MODE, enabled).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
static VoiceOption[] getTtsVoiceOptions(boolean isEnglish) {
|
static VoiceOption[] getTtsVoiceOptions(boolean isEnglish, String provider) {
|
||||||
return isEnglish ? ENGLISH_VOICES : SPANISH_VOICES;
|
if (provider == null || provider.equals(TTS_PROVIDER_GLEEZE)) {
|
||||||
|
return isEnglish ? ENGLISH_VOICES_GLEEZE : SPANISH_VOICES_GLEEZE;
|
||||||
|
} else {
|
||||||
|
return isEnglish ? ENGLISH_VOICES_MISTRAL : SPANISH_VOICES_MISTRAL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getTtsVoice(Context context, boolean isEnglish) {
|
static VoiceOption[] getTtsVoiceOptions(boolean isEnglish) {
|
||||||
String defaultVoice = getDefaultTtsVoice(isEnglish);
|
return getTtsVoiceOptions(isEnglish, TTS_PROVIDER_GLEEZE);
|
||||||
String voice = preferences(context).getString(getTtsVoiceKey(isEnglish), defaultVoice);
|
}
|
||||||
if (isValidTtsVoice(isEnglish, voice)) {
|
|
||||||
|
static String getTtsVoice(Context context, boolean isEnglish, String provider) {
|
||||||
|
String defaultVoice = getDefaultTtsVoice(isEnglish, provider);
|
||||||
|
String voice = preferences(context).getString(getTtsVoiceKey(isEnglish, provider), defaultVoice);
|
||||||
|
if (isValidTtsVoice(isEnglish, provider, voice)) {
|
||||||
return voice;
|
return voice;
|
||||||
}
|
}
|
||||||
return defaultVoice;
|
return defaultVoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setTtsVoice(Context context, boolean isEnglish, String voiceId) {
|
static String getTtsVoice(Context context, boolean isEnglish) {
|
||||||
String voice = isValidTtsVoice(isEnglish, voiceId) ? voiceId : getDefaultTtsVoice(isEnglish);
|
return getTtsVoice(context, isEnglish, TTS_PROVIDER_GLEEZE);
|
||||||
preferences(context).edit().putString(getTtsVoiceKey(isEnglish), voice).apply();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getTtsVoiceLabel(boolean isEnglish, String voiceId) {
|
static void setTtsVoice(Context context, boolean isEnglish, String provider, String voiceId) {
|
||||||
for (VoiceOption option : getTtsVoiceOptions(isEnglish)) {
|
String voice = isValidTtsVoice(isEnglish, provider, voiceId) ? voiceId : getDefaultTtsVoice(isEnglish, provider);
|
||||||
|
preferences(context).edit().putString(getTtsVoiceKey(isEnglish, provider), voice).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setTtsVoice(Context context, boolean isEnglish, String voiceId) {
|
||||||
|
setTtsVoice(context, isEnglish, TTS_PROVIDER_GLEEZE, voiceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getTtsProvider(Context context) {
|
||||||
|
return preferences(context).getString(KEY_TTS_PROVIDER, TTS_PROVIDER_GLEEZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setTtsProvider(Context context, String provider) {
|
||||||
|
preferences(context).edit().putString(KEY_TTS_PROVIDER, provider).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getMistralApiKey(Context context) {
|
||||||
|
return preferences(context).getString(KEY_MISTRAL_API_KEY, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setMistralApiKey(Context context, String apiKey) {
|
||||||
|
preferences(context).edit().putString(KEY_MISTRAL_API_KEY, apiKey).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isMistralConfigured(Context context) {
|
||||||
|
String apiKey = getMistralApiKey(context);
|
||||||
|
return apiKey != null && !apiKey.trim().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getTtsVoiceLabel(boolean isEnglish, String provider, String voiceId) {
|
||||||
|
for (VoiceOption option : getTtsVoiceOptions(isEnglish, provider)) {
|
||||||
if (option.id.equals(voiceId)) {
|
if (option.id.equals(voiceId)) {
|
||||||
return option.label;
|
return option.label;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return getTtsVoiceLabel(isEnglish, getDefaultTtsVoice(isEnglish));
|
return getTtsVoiceLabel(isEnglish, provider, getDefaultTtsVoice(isEnglish));
|
||||||
}
|
}
|
||||||
|
|
||||||
static int getTtsVoiceIndex(boolean isEnglish, String voiceId) {
|
static String getTtsVoiceLabel(boolean isEnglish, String voiceId) {
|
||||||
VoiceOption[] options = getTtsVoiceOptions(isEnglish);
|
return getTtsVoiceLabel(isEnglish, TTS_PROVIDER_GLEEZE, voiceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getTtsVoiceIndex(boolean isEnglish, String provider, String voiceId) {
|
||||||
|
VoiceOption[] options = getTtsVoiceOptions(isEnglish, provider);
|
||||||
for (int index = 0; index < options.length; index++) {
|
for (int index = 0; index < options.length; index++) {
|
||||||
if (options[index].id.equals(voiceId)) {
|
if (options[index].id.equals(voiceId)) {
|
||||||
return index;
|
return index;
|
||||||
@@ -135,19 +198,39 @@ final class UpdateManager {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getDefaultTtsVoice(boolean isEnglish) {
|
static int getTtsVoiceIndex(boolean isEnglish, String voiceId) {
|
||||||
|
return getTtsVoiceIndex(isEnglish, TTS_PROVIDER_GLEEZE, voiceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getDefaultTtsVoice(boolean isEnglish, String provider) {
|
||||||
|
if (provider == null || provider.equals(TTS_PROVIDER_GLEEZE)) {
|
||||||
return isEnglish ? DEFAULT_TTS_VOICE_ENGLISH : DEFAULT_TTS_VOICE_SPANISH;
|
return isEnglish ? DEFAULT_TTS_VOICE_ENGLISH : DEFAULT_TTS_VOICE_SPANISH;
|
||||||
|
} else {
|
||||||
|
return isEnglish ? DEFAULT_TTS_VOICE_ENGLISH_MISTRAL : DEFAULT_TTS_VOICE_SPANISH_MISTRAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getDefaultTtsVoice(boolean isEnglish) {
|
||||||
|
return getDefaultTtsVoice(isEnglish, TTS_PROVIDER_GLEEZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getTtsVoiceKey(boolean isEnglish, String provider) {
|
||||||
|
if (provider == null || provider.equals(TTS_PROVIDER_GLEEZE)) {
|
||||||
|
return isEnglish ? KEY_TTS_VOICE_ENGLISH : KEY_TTS_VOICE_SPANISH;
|
||||||
|
} else {
|
||||||
|
return isEnglish ? KEY_TTS_VOICE_ENGLISH + "_mistral" : KEY_TTS_VOICE_SPANISH + "_mistral";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getTtsVoiceKey(boolean isEnglish) {
|
private static String getTtsVoiceKey(boolean isEnglish) {
|
||||||
return isEnglish ? KEY_TTS_VOICE_ENGLISH : KEY_TTS_VOICE_SPANISH;
|
return getTtsVoiceKey(isEnglish, TTS_PROVIDER_GLEEZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isValidTtsVoice(boolean isEnglish, String voiceId) {
|
private static boolean isValidTtsVoice(boolean isEnglish, String provider, String voiceId) {
|
||||||
if (voiceId == null) {
|
if (voiceId == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (VoiceOption option : getTtsVoiceOptions(isEnglish)) {
|
for (VoiceOption option : getTtsVoiceOptions(isEnglish, provider)) {
|
||||||
if (option.id.equals(voiceId)) {
|
if (option.id.equals(voiceId)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -155,6 +238,10 @@ final class UpdateManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isValidTtsVoice(boolean isEnglish, String voiceId) {
|
||||||
|
return isValidTtsVoice(isEnglish, TTS_PROVIDER_GLEEZE, voiceId);
|
||||||
|
}
|
||||||
|
|
||||||
static UpdateInfo fetchLatestUpdate(Context context) throws Exception {
|
static UpdateInfo fetchLatestUpdate(Context context) throws Exception {
|
||||||
String jsonText = download(LATEST_JSON_URL, "application/json");
|
String jsonText = download(LATEST_JSON_URL, "application/json");
|
||||||
JSONObject json = new JSONObject(jsonText);
|
JSONObject json = new JSONObject(jsonText);
|
||||||
|
|||||||
Reference in New Issue
Block a user