fix(voices): /voices reflète l'inventaire CosyVoice (.cvps), pas les vestiges Qwen3

voicesCursor() marquait une voix « ready » dès que ses embeddings Qwen3
(_voice_prefix/suffix.bin) existaient — des vestiges morts depuis la bascule
CosyVoice → des voix fantômes « prête » sans aucun .cvps (incident zelda).

Désormais : union (WAV = « à convertir ») ∪ (cosyvoice/*.cvps = « prête ») ;
les .bin Qwen3 ne sont plus comptés ; nouvelle colonne cvps_exists (additive).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kazeia Team 2026-06-21 08:51:56 +02:00
parent 613c7efc66
commit 8bcf9f8ebe
1 changed files with 20 additions and 8 deletions

View File

@ -79,6 +79,11 @@ class KazeiaTelemetryProvider : ContentProvider() {
get() = "${com.kazeia.KazeiaApplication.MODELS_DIR}/qwen3-tts-npu" get() = "${com.kazeia.KazeiaApplication.MODELS_DIR}/qwen3-tts-npu"
private const val PREFIX_SUFFIX = "_voice_prefix.bin" private const val PREFIX_SUFFIX = "_voice_prefix.bin"
private const val SUFFIX_SUFFIX = "_voice_suffix.bin" private const val SUFFIX_SUFFIX = "_voice_suffix.bin"
// Voix CosyVoice (prod) : artefacts `.cvps` enrôlés. C'est l'inventaire réel
// depuis la bascule ; les `_voice_*.bin` ci-dessus sont des vestiges Qwen3.
private val VOICE_CVPS_DIR: String
get() = "${com.kazeia.KazeiaApplication.MODELS_DIR}/cosyvoice"
private const val CVPS_SUFFIX = ".cvps"
} }
private val matcher = UriMatcher(UriMatcher.NO_MATCH).apply { private val matcher = UriMatcher(UriMatcher.NO_MATCH).apply {
@ -526,26 +531,32 @@ class KazeiaTelemetryProvider : ContentProvider() {
private fun voicesCursor(): Cursor { private fun voicesCursor(): Cursor {
val cols = arrayOf( val cols = arrayOf(
"id", "name", "state", "id", "name", "state",
"wav_exists", "prefix_exists", "suffix_exists", "wav_exists", "prefix_exists", "suffix_exists", "cvps_exists",
"wav_size_bytes", "wav_duration_seconds", "wav_path", "created_at" "wav_size_bytes", "wav_duration_seconds", "wav_path", "created_at"
) )
val cursor = MatrixCursor(cols) val cursor = MatrixCursor(cols)
// Set des ids = union (wav basenames) (prefix bin basenames) // Inventaire = réalité CosyVoice : union (WAV = « à convertir »)
// (`.cvps` = « prête »). Les embeddings Qwen3 (`_voice_*.bin`) NE sont plus
// comptés dans l'union : vestiges morts depuis la bascule, ils faisaient
// apparaître des voix « prête » fantômes sans aucun `.cvps`.
val wavDir = File(VOICE_WAV_DIR) val wavDir = File(VOICE_WAV_DIR)
val embDir = File(VOICE_EMB_DIR) val cvpsDir = File(VOICE_CVPS_DIR)
val wavIds = (wavDir.listFiles { f -> f.name.endsWith(".wav") } ?: emptyArray()) val wavIds = (wavDir.listFiles { f -> f.name.endsWith(".wav") } ?: emptyArray())
.associateBy { it.nameWithoutExtension.lowercase() } .associateBy { it.nameWithoutExtension.lowercase() }
val prefixIds = (embDir.listFiles { f -> f.name.endsWith(PREFIX_SUFFIX) } ?: emptyArray()) val cvpsIds = (cvpsDir.listFiles { f -> f.name.endsWith(CVPS_SUFFIX) } ?: emptyArray())
.associateBy { it.name.removeSuffix(PREFIX_SUFFIX).lowercase() } .associateBy { it.name.removeSuffix(CVPS_SUFFIX).lowercase() }
val ids = (wavIds.keys + prefixIds.keys).sorted() val ids = (wavIds.keys + cvpsIds.keys).sorted()
for (id in ids) { for (id in ids) {
val wav = wavIds[id] val wav = wavIds[id]
val cvps = cvpsIds[id]
val prefix = File(VOICE_EMB_DIR, "${id}$PREFIX_SUFFIX") val prefix = File(VOICE_EMB_DIR, "${id}$PREFIX_SUFFIX")
val suffix = File(VOICE_EMB_DIR, "${id}$SUFFIX_SUFFIX") val suffix = File(VOICE_EMB_DIR, "${id}$SUFFIX_SUFFIX")
// « ready » = `.cvps` déployé (jouable par CosyVoice) ; « recorded » =
// WAV seul (à convertir/enrôler par Kazeia-central) ; sinon « error ».
val state = when { val state = when {
prefix.exists() && suffix.exists() -> "ready" cvps != null -> "ready"
wav != null -> "recorded" wav != null -> "recorded"
else -> "error" else -> "error"
} }
@ -557,10 +568,11 @@ class KazeiaTelemetryProvider : ContentProvider() {
if (wav != null) 1 else 0, if (wav != null) 1 else 0,
if (prefix.exists()) 1 else 0, if (prefix.exists()) 1 else 0,
if (suffix.exists()) 1 else 0, if (suffix.exists()) 1 else 0,
if (cvps != null) 1 else 0,
wav?.length() ?: 0L, wav?.length() ?: 0L,
durSec, durSec,
wav?.absolutePath ?: "", wav?.absolutePath ?: "",
wav?.lastModified() ?: prefix.lastModified() wav?.lastModified() ?: cvps?.lastModified() ?: 0L
)) ))
} }
return cursor return cursor