chatterbox / TtsClientHF.kt
Abdo96's picture
Upload 4 files
d9d5160 verified
Raw
History Blame Contribute Delete
5.32 kB
// TtsClientHF.kt — عميل أندرويد لـ Hugging Face Space (Gradio API)
// يستبدل TtsClient.kt السابق
//
// build.gradle:
// implementation("com.squareup.okhttp3:okhttp:4.12.0")
// implementation("org.json:json:20240303") // أو استخدم kotlinx-serialization
// AndroidManifest.xml:
// <uses-permission android:name="android.permission.INTERNET" />
package com.example.ttsapp
import android.content.Context
import android.media.MediaPlayer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONArray
import org.json.JSONObject
import java.io.File
import java.util.concurrent.TimeUnit
class TtsClientHF(
private val context: Context,
// مثال: https://ahmed-tts-app.hf.space (بدون / في النهاية)
private val spaceUrl: String = "https://USERNAME-SPACENAME.hf.space",
// فقط إذا كان الـ Space خاصاً (Private): توكن من إعدادات حسابك في HF
private val hfToken: String? = null,
) {
private val client = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(180, TimeUnit.SECONDS) // أول طلب بعد خمول يوقظ الـ Space
.build()
private var player: MediaPlayer? = null
suspend fun speak(
text: String,
language: String = "ar",
exaggeration: Double = 0.5,
cfgWeight: Double = 0.5,
) {
val file = synthesize(text, language, exaggeration, cfgWeight)
withContext(Dispatchers.Main) { play(file) }
}
/**
* Gradio API يعمل على خطوتين:
* 1) POST /gradio_api/call/tts → يرجع event_id
* 2) GET /gradio_api/call/tts/{event_id} → بث SSE، آخر سطر data يحوي رابط الملف
* 3) تنزيل ملف الصوت من الرابط
*/
suspend fun synthesize(
text: String,
language: String,
exaggeration: Double,
cfgWeight: Double,
): File = withContext(Dispatchers.IO) {
// ---- الخطوة 1: إرسال الطلب ----
val payload = JSONObject().put(
"data",
JSONArray().put(text).put(language)
.put(exaggeration).put(cfgWeight)
.put(JSONObject.NULL) // voice_ref: نستخدم الصوت الافتراضي
)
val callReq = authed(
Request.Builder()
.url("$spaceUrl/gradio_api/call/tts")
.post(payload.toString().toRequestBody("application/json".toMediaType()))
).build()
val eventId = client.newCall(callReq).execute().use { resp ->
check(resp.isSuccessful) { "TTS call failed: HTTP ${resp.code}" }
JSONObject(resp.body!!.string()).getString("event_id")
}
// ---- الخطوة 2: قراءة النتيجة من بث SSE ----
val resultReq = authed(
Request.Builder().url("$spaceUrl/gradio_api/call/tts/$eventId").get()
).build()
var audioUrl: String? = null
client.newCall(resultReq).execute().use { resp ->
check(resp.isSuccessful) { "TTS result failed: HTTP ${resp.code}" }
val reader = resp.body!!.byteStream().bufferedReader()
var event = ""
reader.forEachLine { line ->
when {
line.startsWith("event:") -> event = line.removePrefix("event:").trim()
line.startsWith("data:") && event == "complete" -> {
val data = JSONArray(line.removePrefix("data:").trim())
audioUrl = data.getJSONObject(0).getString("url")
}
line.startsWith("data:") && event == "error" ->
throw Exception("TTS server error: ${line.removePrefix("data:").trim()}")
}
}
}
val url = audioUrl ?: throw Exception("No audio returned")
// ---- الخطوة 3: تنزيل الصوت ----
val dlReq = authed(Request.Builder().url(url).get()).build()
client.newCall(dlReq).execute().use { resp ->
check(resp.isSuccessful) { "Audio download failed: HTTP ${resp.code}" }
val out = File(context.cacheDir, "speech_${System.currentTimeMillis()}.wav")
out.outputStream().use { resp.body!!.byteStream().copyTo(it) }
out
}
}
private fun authed(b: Request.Builder): Request.Builder =
if (hfToken != null) b.header("Authorization", "Bearer $hfToken") else b
private fun play(file: File) {
player?.release()
player = MediaPlayer().apply {
setDataSource(file.absolutePath)
setOnCompletionListener { file.delete() }
prepare(); start()
}
}
fun stop() { player?.release(); player = null }
}
/* ========= الاستخدام =========
val tts = TtsClientHF(context, spaceUrl = "https://ahmed-tts.hf.space")
lifecycleScope.launch {
tts.speak("أهلاً وسهلاً بكم", language = "العربية (ar)") // أو "ar" مباشرة
}
============================== */