feat: sincronizado o estado de transcrição, métricas e diarização

- sincronizado o estado de transcrição, métricas e diarização entre SQLite, Scanner e Editar vídeo

Resumo:
- 6 arquivos alterados
- 1 novos
- 5 modificados
- 0 removidos

 5 files changed, 181 insertions(+), 12 deletions(-)

Arquivos:
  - code/cep-plugin/index.html
  - code/cep-plugin/main.js
  - code/cep-plugin/styles.css
  - code/engine/persistencia/consultas.py
  - code/engine/testes/test_consultas_de_persistencia.py
  - code/engine/consultar_status_da_analise.py
This commit is contained in:
João Henrique
2026-09-09 19:45:15 -04:00
parent c688d0eabf
commit 19d10e61a1
6 changed files with 220 additions and 12 deletions
@@ -0,0 +1,39 @@
"""Consulta o estado editorial persistido de uma análise do Scanner."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
import sys
CAMINHO_DO_CODIGO = Path(__file__).resolve().parent.parent
if str(CAMINHO_DO_CODIGO) not in sys.path:
sys.path.insert(0, str(CAMINHO_DO_CODIGO))
from engine.persistencia.consultas import ConsultasDeAnalises
def executar(caminho_do_banco: Path, video_id: str) -> dict:
"""Consulta o banco e devolve o estado da análise solicitada."""
resultado = ConsultasDeAnalises(caminho_do_banco).consultar_status_da_analise(video_id)
if resultado is None:
raise ValueError(f"Vídeo não encontrado no banco: {video_id}")
return resultado
def principal() -> None:
"""Executa a consulta pela linha de comando."""
argumentos = argparse.ArgumentParser()
argumentos.add_argument("video_id")
argumentos.add_argument("--banco", type=Path, default=Path(".jhonny/analises.db"))
opcoes = argumentos.parse_args()
try:
print(json.dumps(executar(opcoes.banco, opcoes.video_id), ensure_ascii=False))
except (OSError, ValueError) as erro:
print(json.dumps({"erro": str(erro)}, ensure_ascii=False))
raise SystemExit(1) from erro
if __name__ == "__main__":
principal()
+39
View File
@@ -96,6 +96,45 @@ class ConsultasDeAnalises:
"total_cenas": contagens["cenas"],
}
def consultar_status_da_analise(self, video_id: str) -> dict | None:
"""Devolve o estado compacto das análises de áudio de um vídeo."""
self._validar_texto_obrigatorio("video_id", video_id)
resumo = self.consultar_resumo_do_video(video_id)
if resumo is None:
return None
linha = self.conexao.execute(
"""SELECT
COUNT(*) AS falas,
SUM(CASE WHEN falante IS NOT NULL AND TRIM(falante) <> '' THEN 1 ELSE 0 END) AS falas_com_falante,
SUM(CASE WHEN caracteristicas_acusticas IS NOT NULL
AND caracteristicas_acusticas <> '{}'
THEN 1 ELSE 0 END) AS falas_com_metricas
FROM segmentos_de_transcricao
WHERE video_id = ?""",
(video_id,),
).fetchone()
falantes = [
item["falante"] for item in self.conexao.execute(
"""SELECT DISTINCT falante
FROM segmentos_de_transcricao
WHERE video_id = ? AND falante IS NOT NULL AND TRIM(falante) <> ''
ORDER BY falante""",
(video_id,),
).fetchall()
]
return {
**resumo,
"audio": {
"transcricao_disponivel": bool(linha["falas"]),
"total_falas": linha["falas"] or 0,
"falas_com_falante": linha["falas_com_falante"] or 0,
"falas_com_metricas": linha["falas_com_metricas"] or 0,
"diarizacao_disponivel": bool(linha["falas_com_falante"]),
"metricas_de_voz_disponiveis": bool(linha["falas_com_metricas"]),
"falantes": falantes,
},
}
def listar_falas_no_intervalo(
self,
video_id: str,
@@ -95,6 +95,16 @@ class TesteConsultasDeAnalises(unittest.TestCase):
with self.assertRaises(ErroDeConsultaInvalida):
self.consultas.consultar_resumo_do_video("")
def test_consultar_status_da_analise_expoe_falantes_e_metricas(self) -> None:
status = self.consultas.consultar_status_da_analise(VIDEO_ID)
self.assertEqual(status["audio"]["total_falas"], 1)
self.assertEqual(status["audio"]["falas_com_falante"], 1)
self.assertEqual(status["audio"]["falas_com_metricas"], 0)
self.assertTrue(status["audio"]["diarizacao_disponivel"])
self.assertFalse(status["audio"]["metricas_de_voz_disponiveis"])
self.assertEqual(status["audio"]["falantes"], ["locutor"])
# --- listar_falas_no_intervalo ------------------------------------------------------
def test_listar_falas_no_intervalo_filtra_por_tempo_e_omite_palavras_por_padrao(self) -> None: