⎇ 81bd1537ab8e172ed59193ef9e316ad76f44b778
vela/src/main/java/com/rizoma/vela/data/Repositorio.kt
History
raw
vela: modo reunión — transcripción offline por fragmento (Vosk)
1package com.rizoma.vela.data
2
3import android.content.Context
4import org.json.JSONArray
5import org.json.JSONObject
6import java.io.File
7
8/**
9 * Guarda y lee las sesiones en `filesDir/sesiones/<id>/`. Cada sesión es una
10 * carpeta con `audio.m4a` y `log.json`. Sin base de datos ni deps: JSON crudo
11 * con escritura atómica (`.tmp` + rename), como el resto del rizoma.
12 */
13class Repositorio(context: Context) {
14
15 private val base = File(context.filesDir, "sesiones")
16
17 fun carpeta(id: Long): File = File(base, id.toString()).apply { mkdirs() }
18
19 /** Escribe (o reescribe) el log de una sesión de forma atómica. */
20 fun escribirLog(
21 id: Long,
22 inicioEpoch: Long,
23 sampleRate: Int,
24 modo: Modo,
25 duracionRealMs: Long,
26 duracionSonoraMs: Long,
27 segmentos: List<Segmento>,
28 ) {
29 val root = JSONObject().apply {
30 put("id", id)
31 put("inicioEpoch", inicioEpoch)
32 put("sampleRate", sampleRate)
33 put("modo", modo.name)
34 put("duracionRealMs", duracionRealMs)
35 put("duracionSonoraMs", duracionSonoraMs)
36 put("segmentos", JSONArray().apply {
37 segmentos.forEach { s ->
38 put(JSONObject().apply {
39 put("i", s.indice)
40 put("ini", s.inicioRealMs)
41 put("dur", s.duracionMs)
42 put("arch", s.archivoMs)
43 put("pico", s.picoDb.toDouble())
44 put("medio", s.medioDb.toDouble())
45 if (!s.texto.isNullOrBlank()) put("txt", s.texto)
46 })
47 }
48 })
49 }
50 val dir = carpeta(id)
51 val tmp = File(dir, "log.json.tmp")
52 tmp.writeText(root.toString())
53 tmp.renameTo(File(dir, "log.json"))
54 }
55
56 /** Lista las sesiones (más recientes primero), sin cargar los segmentos. */
57 fun listar(): List<Sesion> {
58 val dirs = base.listFiles()?.filter { it.isDirectory } ?: return emptyList()
59 return dirs.mapNotNull { cargarDe(it) }
60 .sortedByDescending { it.inicioEpoch }
61 }
62
63 fun cargar(id: Long): Sesion? = cargarDe(File(base, id.toString()))
64
65 private fun cargarDe(dir: File): Sesion? {
66 val log = File(dir, "log.json")
67 if (!log.exists()) return null
68 return try {
69 val o = JSONObject(log.readText())
70 val arr = o.optJSONArray("segmentos") ?: JSONArray()
71 val segs = ArrayList<Segmento>(arr.length())
72 for (i in 0 until arr.length()) {
73 val s = arr.getJSONObject(i)
74 segs.add(
75 Segmento(
76 indice = s.optInt("i", i),
77 inicioRealMs = s.optLong("ini"),
78 duracionMs = s.optLong("dur"),
79 archivoMs = s.optLong("arch"),
80 picoDb = s.optDouble("pico", -70.0).toFloat(),
81 medioDb = s.optDouble("medio", -70.0).toFloat(),
82 texto = s.optString("txt", "").ifBlank { null },
83 )
84 )
85 }
86 Sesion(
87 id = o.getLong("id"),
88 inicioEpoch = o.getLong("inicioEpoch"),
89 sampleRate = o.optInt("sampleRate", 16000),
90 modo = runCatching { Modo.valueOf(o.optString("modo", "NORMAL")) }
91 .getOrDefault(Modo.NORMAL),
92 duracionRealMs = o.optLong("duracionRealMs"),
93 duracionSonoraMs = o.optLong("duracionSonoraMs"),
94 segmentos = segs,
95 dir = dir,
96 )
97 } catch (_: Exception) {
98 null
99 }
100 }
101
102 fun borrar(id: Long) {
103 File(base, id.toString()).deleteRecursively()
104 }
105}