feat: adicionadas métricas opcionais de fala ao Scanner com anális

- adicionadas métricas opcionais de fala ao Scanner com análise acústica na engine

Resumo:
- 7 arquivos alterados
- 2 novos
- 5 modificados
- 0 removidos

 5 files changed, 43 insertions(+), 6 deletions(-)

Arquivos:
  - code/cep-plugin/index.html
  - code/cep-plugin/main.js
  - code/engine/executar_scanner.py
  - code/engine/scanner/configuracao_visual.py
  - code/engine/scanner/transcricao_da_timeline.py
  - code/engine/integracoes/audio/
  - code/engine/testes/test_analisador_de_metricas_de_voz.py
This commit is contained in:
João Henrique
2026-09-09 17:17:55 -04:00
parent 89288d95f0
commit b03b175973
8 changed files with 241 additions and 6 deletions
@@ -39,6 +39,7 @@ class ConfiguracaoVisualDoScanner:
faixas_de_video: tuple[int, ...] = ()
faixas_de_audio: tuple[int, ...] = ()
modo_de_analise: str = "ambos"
metricas_de_fala: bool = False
recursos_vision: frozenset[str] = RECURSOS_VISION
detectar_cenas: bool = True
analisar_continuidade: bool = True
@@ -87,6 +88,7 @@ class ConfiguracaoVisualDoScanner:
faixas_de_video=tuple(sorted(set(int(item) for item in dados.get("faixas_de_video", ())))),
faixas_de_audio=tuple(sorted(set(int(item) for item in dados.get("faixas_de_audio", ())))),
modo_de_analise=str(dados.get("modo_de_analise", "ambos")),
metricas_de_fala=bool(dados.get("metricas_de_fala", False)),
recursos_vision=frozenset(dados.get("recursos_vision", RECURSOS_VISION)),
detectar_cenas=bool(dados.get("detectar_cenas", True)),
analisar_continuidade=bool(dados.get("analisar_continuidade", True)),
@@ -108,6 +110,7 @@ class ConfiguracaoVisualDoScanner:
"faixas_de_video": list(self.faixas_de_video),
"faixas_de_audio": list(self.faixas_de_audio),
"modo_de_analise": self.modo_de_analise,
"metricas_de_fala": self.metricas_de_fala,
"recursos_vision": sorted(self.recursos_vision),
"detectar_cenas": self.detectar_cenas,
"analisar_continuidade": self.analisar_continuidade,
+24 -3
View File
@@ -34,12 +34,14 @@ class TranscricaoDaTimeline:
def __init__(self, provider: Any, extrator: ExtracaoDeAudio | None = None,
diretoria_de_trabalho: str | Path = ".transcricao",
analisador_de_emocao: Any | None = None,
diarizador: Any | None = None) -> None:
diarizador: Any | None = None,
analisador_de_metricas_de_voz: Any | None = None) -> None:
self.provider = provider
self.extrator = extrator or ExtracaoDeAudio()
self.diretoria_de_trabalho = Path(diretoria_de_trabalho)
self.analisador_de_emocao = analisador_de_emocao
self.diarizador = diarizador
self.analisador_de_metricas_de_voz = analisador_de_metricas_de_voz
def executar(self, timeline: Any) -> list[TranscricaoDoClipe]:
clipes: list[Any] = []
@@ -113,15 +115,23 @@ class TranscricaoDaTimeline:
dados = json.loads(cache.read_text(encoding="utf-8"))
if all("palavras" in item and
(self.analisador_de_emocao is None or
("emocao" in item and "voz_aparente" in item))
("emocao" in item and "voz_aparente" in item)) and
(self.analisador_de_metricas_de_voz is None or
"caracteristicas_acusticas" in item)
for item in dados):
return [self._segmento_do_json(item) for item in dados]
partes = self.extrator.extrair(arquivo, pasta / "audio")
segmentos: list[SegmentoDeTranscricao] = []
fim_anterior: float | None = None
for parte in partes:
falas = self._diarizar(parte.caminho)
for segmento in self.provider.transcrever(type("Audio", (), {"arquivo": str(parte.caminho)})()):
analise = self._analisar_emocao(parte.caminho, segmento.inicio, segmento.fim)
inicio_absoluto = parte.inicio + segmento.inicio
metricas_de_voz = self._analisar_metricas_de_voz(
parte.caminho, segmento.inicio, segmento.fim, segmento.palavras,
inicio_absoluto, fim_anterior,
)
falante = self._falante_em(falas, segmento.inicio, segmento.fim)
segmentos.append(SegmentoDeTranscricao(parte.inicio + segmento.inicio,
parte.inicio + segmento.fim, segmento.texto, segmento.confianca,
@@ -130,8 +140,9 @@ class TranscricaoDaTimeline:
self._falante_em(falas, p.inicio, p.fim))
for p in segmento.palavras),
analise.get("emocao"), analise.get("confianca"),
dict(analise.get("caracteristicas_acusticas", {})), falante,
metricas_de_voz or dict(analise.get("caracteristicas_acusticas", {})), falante,
analise.get("voz_aparente"), analise.get("confianca_voz")))
fim_anterior = inicio_absoluto + segmento.fim - segmento.inicio
pasta.mkdir(parents=True, exist_ok=True)
cache.write_text(json.dumps([asdict(item) for item in segmentos], ensure_ascii=False, indent=2), encoding="utf-8")
(pasta / f"{prefixo}.txt").write_text(
@@ -157,6 +168,16 @@ class TranscricaoDaTimeline:
return {}
return dict(self.analisador_de_emocao.analisar(arquivo, inicio, fim))
def _analisar_metricas_de_voz(self, arquivo: Path, inicio: float, fim: float,
palavras: tuple[PalavraDeTranscricao, ...],
inicio_absoluto: float,
fim_anterior: float | None) -> dict[str, Any]:
"""Calcula métricas acústicas somente quando foram solicitadas."""
if self.analisador_de_metricas_de_voz is None:
return {}
return dict(self.analisador_de_metricas_de_voz.analisar(
arquivo, inicio, fim, palavras, inicio_absoluto, fim_anterior))
def _diarizar(self, arquivo: Path) -> list[tuple[float, float, str]]:
if self.diarizador is None:
return []