criado repositório jhonny-editor no Gitea adicionado script admin/deploy.command com commit automático atualizado admin/DEV-NOTES.md com template limpo Resumo: - 48 arquivos alterados - 26 novos - 19 modificados - 3 removidos 22 files changed, 658 insertions(+), 568 deletions(-) Arquivos: - AGENTS.md - admin/DEV-NOTES.md - admin/OpenCut.command - admin/commit.command - admin/deploy.command - admin/update.command - code/cep-plugin/index.html - code/cep-plugin/main.js - code/cep-plugin/styles.css - code/engine/ARQUITETURA.md - code/engine/arquitetura/README.md - code/engine/gerar_relatorio_timeline.py - code/engine/integracoes/apple_speech/apple_speech_transcriber.swift - code/engine/integracoes/apple_speech/provider_de_transcricao_apple.py - code/engine/integracoes/midia/__init__.py - code/engine/integracoes/whisper/provider_de_transcricao_local.py - code/engine/scanner/__init__.py - code/engine/scanner/coordenacao/__init__.py - code/engine/scanner/descoberta/__init__.py - code/engine/scanner/modelos.py - code/engine/scanner/transcricao_da_timeline.py - code/src/tools/discovery.ts - :memory:.ses - admin/Jhonny.command - admin/inativos/OpenCut.command - admin/inativos/update.command - code/docs/glossario-analise-emocional.md - code/docs/levantamento-apple-vision-e-apple-intelligence.md - code/docs/levantamento-ferramentas-analise-visual-local.md - code/engine/arquitetura/biblioteca-inteligente-de-videos.md - code/engine/executar_scanner.py - code/engine/integracoes/huggingface/ - code/engine/integracoes/midia/extracao_de_metadados.py - code/engine/integracoes/visual/ - code/engine/requirements-visual.txt - code/engine/scanner/configuracao_visual.py - code/engine/scanner/descoberta/descoberta_de_arquivos.py - code/engine/scanner/metadados.py - code/engine/scanner/relatorio_visual.py - code/engine/scanner/retakes/ - code/engine/scanner/visual.py - code/engine/testes/test_analise_visual_local.py - code/engine/testes/test_arquivos_e_metadados.py - code/engine/testes/test_provider_de_transcricao_apple.py - code/relatorios/analise-brools/ - code/relatorios/analise-visual/ - code/relatorios/audio/arquivos/ - code/relatorios/transcricao-timeline.md
154 lines
4.2 KiB
Python
154 lines
4.2 KiB
Python
"""Modelos de dados do submódulo de detecção de retakes.
|
|
|
|
Mantemos os mesmos princípios de ``scanner/modelos.py``: dataclasses
|
|
imutáveis (``frozen=True``) com validação em ``__post_init__`` e sem
|
|
dependência de implementações concretas de providers.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime, timezone
|
|
|
|
from ..modelos import PalavraDeTranscricao
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Fala:
|
|
"""Uma fala normalizada, já posicionada na timeline do vídeo.
|
|
|
|
Corresponde a um ``SegmentoDeTranscricao`` convertido pelo adaptador,
|
|
mas carrega o contexto necessário para a análise temporal e de
|
|
agrupamento: vídeo, faixa de áudio, número de sequência e o vínculo
|
|
com o segmento/clipe de origem.
|
|
"""
|
|
|
|
id: str
|
|
video_id: str
|
|
faixa_id: str
|
|
segmento_id: str
|
|
ordem: int
|
|
inicio: float
|
|
fim: float
|
|
texto: str
|
|
texto_normalizado: str = ""
|
|
palavras: tuple[PalavraDeTranscricao, ...] = ()
|
|
falante: str | None = None
|
|
|
|
def __post_init__(self) -> None:
|
|
if self.inicio < 0 or self.fim < self.inicio:
|
|
raise ValueError(f"Intervalo da fala {self.id} inválido.")
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SinalDeReinicio:
|
|
"""Indício de que uma fala recomeçou uma ideia já iniciada."""
|
|
|
|
fala_id: str
|
|
tipo: str
|
|
intensidade: float
|
|
evidencias: list[str] = field(default_factory=list)
|
|
|
|
def __post_init__(self) -> None:
|
|
if not 0.0 <= self.intensidade <= 1.0:
|
|
raise ValueError(f"Intensidade do reinício {self.fala_id} fora de [0, 1].")
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ResultadoDaComparacao:
|
|
"""Resultado da comparação de duas falas, textual e por sequência."""
|
|
|
|
fala_a_id: str
|
|
fala_b_id: str
|
|
similaridade_jaccard: float = 0.0
|
|
similaridade_de_sequencia: float = 0.0
|
|
similaridade_do_inicio: float = 0.0
|
|
similaridade_do_final: float = 0.0
|
|
similaridade_final: float = 0.0
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ResultadoDoIntervalo:
|
|
"""Relação temporal entre duas falas."""
|
|
|
|
intervalo_em_segundos: float
|
|
mesmo_segmento: bool = False
|
|
proximidade_temporal: float = 0.0
|
|
indicio_de_nova_tentativa: bool = False
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class EvidenciaDeRetake:
|
|
"""Uma evidência legível que sustenta a classificação de um grupo."""
|
|
|
|
tipo: str
|
|
descricao: str
|
|
valor: float | None = None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TomadaDeRetake:
|
|
"""Uma tomada (fala) pertencente a um grupo de retakes."""
|
|
|
|
ordem: int
|
|
fala_id: str
|
|
segmento_id: str
|
|
inicio: float
|
|
fim: float
|
|
texto: str
|
|
similaridade_com_anterior: float = 0.0
|
|
confianca: float = 0.0
|
|
|
|
|
|
def gerar_id_do_grupo() -> str:
|
|
"""Gera um id curto e monótono para um grupo de retakes."""
|
|
return f"retake_{uuid.uuid4().hex[:6]}"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ParAgrupado:
|
|
"""Um par de falas que o agrupador juntou como candidato a retake."""
|
|
|
|
fala_a: Fala
|
|
fala_b: Fala
|
|
comparacao: ResultadoDaComparacao
|
|
intervalo: ResultadoDoIntervalo
|
|
similaridade_semantica: float = 0.0
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class AgrupamentoDeFalas:
|
|
"""Um grupo candidato formado pelo agrupador, antes da classificação."""
|
|
|
|
fala_ids: tuple[str, ...]
|
|
pares: tuple[ParAgrupado, ...]
|
|
sinais_de_reinicio: tuple[SinalDeReinicio, ...] = ()
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class GrupoDeRetake:
|
|
"""Agrupa as tomadas que representam tentativas da mesma fala."""
|
|
|
|
id: str
|
|
video_id: str
|
|
faixa_id: str
|
|
tipo: str
|
|
confianca: float
|
|
tomadas: list[TomadaDeRetake] = field(default_factory=list)
|
|
evidencias: list[EvidenciaDeRetake] = field(default_factory=list)
|
|
criado_em: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
|
|
|
|
def __post_init__(self) -> None:
|
|
if not 0.0 <= self.confianca <= 1.0:
|
|
raise ValueError(f"Confiança do grupo {self.id} fora de [0, 1].")
|
|
|
|
@property
|
|
def tomada_principal_id(self) -> str | None:
|
|
if not self.tomadas:
|
|
return None
|
|
return max(self.tomadas, key=lambda item: item.confianca).fala_id
|
|
|
|
@property
|
|
def fala_ids(self) -> list[str]:
|
|
return [tomada.fala_id for tomada in self.tomadas] |