Skip to content

Commit

Permalink
feat: support for audio using Lingva (closes #126)
Browse files Browse the repository at this point in the history
  • Loading branch information
Bnyro committed Jul 24, 2023
1 parent 3f6c19a commit 637f7aa
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 16 deletions.
18 changes: 16 additions & 2 deletions app/src/main/java/com/bnyro/translate/api/lv/LVEngine.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,17 @@ import com.bnyro.translate.obj.Definition
import com.bnyro.translate.obj.Translation
import com.bnyro.translate.util.RetrofitHelper
import com.bnyro.translate.util.TranslationEngine
import java.io.File
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

class LVEngine : TranslationEngine(
name = "Lingva",
defaultUrl = "https://lingva.ml",
defaultUrl = "https://lingva.lunar.icu",
urlModifiable = true,
apiKeyState = ApiKeyState.DISABLED,
autoLanguageCode = "auto"
autoLanguageCode = "auto",
supportsAudio = true
) {

private lateinit var api: LingvaTranslate
Expand Down Expand Up @@ -67,4 +71,14 @@ class LVEngine : TranslationEngine(
}
)
}

override suspend fun getAudioFile(lang: String, query: String): File? {
val byteArray = api.getAudio(lang, query).toByteArray()
if (byteArray.isEmpty()) return null
return withContext(Dispatchers.IO) {
File.createTempFile("audio", ".mp3").apply {
writeBytes(byteArray)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package com.bnyro.translate.api.lv

import com.bnyro.translate.api.lv.obj.LVAudioResponse
import com.bnyro.translate.api.lv.obj.LVTranslationResponse
import com.bnyro.translate.api.lv.obj.LvLanguage
import retrofit2.http.GET
Expand All @@ -32,4 +33,10 @@ interface LingvaTranslate {
@Path("target") target: String,
@Path("query") query: String
): LVTranslationResponse

@GET("/api/v1/audio/{lang}/{query}")
suspend fun getAudio(
@Path("lang") lang: String,
@Path("query") query: String
): LVAudioResponse
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2023 Bnyro
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package com.bnyro.translate.api.lv.obj

import kotlinx.serialization.Serializable

@Serializable
data class LVAudioResponse(
val audio: List<Int>
) {
fun toByteArray() = this.audio.map { it.toByte() }.toByteArray()
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package com.bnyro.translate.ui.models

import android.content.Context
import android.media.MediaPlayer
import android.net.Uri
import android.os.Handler
import android.os.Looper
Expand All @@ -39,15 +40,15 @@ import com.bnyro.translate.obj.Translation
import com.bnyro.translate.util.JsonHelper
import com.bnyro.translate.util.Preferences
import com.bnyro.translate.util.TessHelper
import com.bnyro.translate.util.TranslationEngine
import java.io.File
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.decodeFromString
import kotlinx.coroutines.withContext
import kotlinx.serialization.encodeToString

class TranslationModel : ViewModel() {
var engine: TranslationEngine = getCurrentEngine()
var engine by mutableStateOf(getCurrentEngine())

var simTranslationEnabled by mutableStateOf(
Preferences.get(Preferences.simultaneousTranslationKey, false)
Expand Down Expand Up @@ -76,6 +77,9 @@ class TranslationModel : ViewModel() {

var translating by mutableStateOf(false)

private var mediaPlayer: MediaPlayer? = null
private var audioFile: File? = null

private fun getLanguageByPrefKey(key: String): Language? {
return runCatching {
JsonHelper.json.decodeFromString<Language>(Preferences.get(key, ""))
Expand Down Expand Up @@ -257,4 +261,35 @@ class TranslationModel : ViewModel() {
JsonHelper.json.encodeToString(targetLanguage)
)
}

fun playAudio() {
releaseMediaPlayer()
viewModelScope.launch(Dispatchers.IO) {
audioFile = runCatching {
engine.getAudioFile(targetLanguage.code, translation.translatedText)
}.getOrElse { return@launch }

withContext(Dispatchers.Main) {
mediaPlayer = MediaPlayer().apply {
setOnCompletionListener {
releaseMediaPlayer()
}
}
audioFile?.let { file ->
mediaPlayer?.apply {
setDataSource(file.absolutePath)
prepare()
start()
}
}
}
}
}

private fun releaseMediaPlayer() {
audioFile?.delete()
audioFile = null
mediaPlayer?.release()
mediaPlayer = null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,19 +140,27 @@ fun TranslationComponent(
)
}

if (viewModel.translation.translatedText.isNotEmpty() && SpeechHelper.ttsAvailable) {
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.CenterEnd
) {
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.CenterEnd
) {
if (viewModel.translation.translatedText.isNotEmpty()) {
if (viewModel.engine.supportsAudio) {
StyledIconButton(
imageVector = Icons.Default.VolumeUp
) {
SpeechHelper.speak(
context,
viewModel.translation.translatedText,
viewModel.targetLanguage.code
)
viewModel.playAudio()
}
} else if (SpeechHelper.ttsAvailable) {
StyledIconButton(
imageVector = Icons.Default.VolumeUp
) {
SpeechHelper.speak(
context,
viewModel.translation.translatedText,
viewModel.targetLanguage.code
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package com.bnyro.translate.util
import com.bnyro.translate.const.ApiKeyState
import com.bnyro.translate.db.obj.Language
import com.bnyro.translate.obj.Translation
import java.io.File
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull

abstract class TranslationEngine(
Expand All @@ -28,7 +29,8 @@ abstract class TranslationEngine(
val urlModifiable: Boolean,
val apiKeyState: ApiKeyState,
val autoLanguageCode: String?,
var supportsSimTranslation: Boolean = true
val supportsSimTranslation: Boolean = true,
val supportsAudio: Boolean = false
) {

abstract fun createOrRecreate(): TranslationEngine
Expand All @@ -48,6 +50,8 @@ abstract class TranslationEngine(
).toHttpUrlOrNull()?.toString() ?: defaultUrl
}

open suspend fun getAudioFile(lang: String, query: String): File? = null

fun getApiKey() = Preferences.get(
apiPrefKey,
""
Expand Down

0 comments on commit 637f7aa

Please sign in to comment.