- reorganizado o fluxo de edição para validar a análise do Scanner, selecionar o tipo de vídeo e enviar suas instruções no JSON. - banco de análises: métricas de fala viraram colunas, busca lexical FTS5, enunciados com embeddings, views achatadas de leitura (fala/palavra/linha do tempo/fala com visual), ingestor do pipeline de voz e gravação idempotente de evidências visuais e cenas. - retakes passam a ser gravados no banco de análises (SQLite) em vez de JSON por caso, com status de revisão persistido. - planos de edição e suas aplicações passam a ser registrados no banco, em vez de se perderem no arquivo temporário. - comando de limpeza das evidências visuais e cenas duplicadas por execuções antigas do Scanner. - análise visual: OpenCV (rostos) e PySceneDetect passam a entrar no detector local por padrão; novo adapter InsightFace gera assinatura facial (embedding) para reconhecer a mesma pessoa entre tomadas, gravada como evidência visual no banco. - corrigido: métricas de fala (energia, pitch, velocidade) agora gravam nas colunas dedicadas, não só no JSON; a view fala+visual passa a casar por vídeo e tempo, já que o mesmo arquivo entra na timeline como clipes distintos de vídeo e áudio; views são recriadas a cada abertura do banco para uma correção de consulta chegar a bancos já existentes. - reordenadas as abas do painel CEP para Scanner, Refinar e Editar vídeo Resumo: - 24 arquivos alterados - 9 novos - 15 modificados - 0 removidos 15 files changed, 872 insertions(+), 29 deletions(-) Arquivos: - .jhonny/analises.db - code/cep-plugin/index.html - code/cep-plugin/main.js - code/engine/aplicar_plano_de_edicao.py - code/engine/integracoes/visual/README.md - code/engine/integracoes/visual/__init__.py - code/engine/integracoes/visual/analisadores.py - code/engine/persistencia/__init__.py - code/engine/persistencia/esquema.py - code/engine/persistencia/repositorio_de_analises_sqlite.py - code/engine/persistencia/repositorio_de_retakes_sqlite.py - code/engine/requirements-visual.txt - code/engine/scanner/configuracao_visual.py - code/engine/scanner/visual.py - code/engine/testes/test_analise_visual_local.py - .jhonny/analises.db.pos-scanner-084841 - code/engine/integracoes/embeddings/ - code/engine/limpar_duplicatas.py - code/engine/persistencia/busca_de_conteudo.py - code/engine/persistencia/enunciados.py - code/engine/persistencia/ingestao_de_voz.py - code/engine/persistencia/limpeza.py - code/engine/persistencia/repositorio_de_planos.py - code/engine/testes/test_busca_de_conteudo.py
94 lines
3.2 KiB
Python
94 lines
3.2 KiB
Python
"""
|
|
Remoção de linhas duplicadas deixadas por execuções não idempotentes.
|
|
|
|
Antes de ``substituir_evidencias_visuais`` e ``substituir_cenas`` existirem, o
|
|
Scanner acrescentava evidências e cenas a cada execução em vez de substituir
|
|
as do clipe. Bancos criados nesse período acumularam cópias exatas da mesma
|
|
observação, o que infla contagens e médias calculadas sobre essas tabelas.
|
|
|
|
Este módulo apaga essas cópias mantendo sempre a linha de menor ``id`` de cada
|
|
grupo idêntico. Só remove duplicata exata — linhas que diferem em qualquer
|
|
campo são observações distintas e são preservadas.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sqlite3
|
|
from dataclasses import dataclass
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ResultadoDaLimpeza:
|
|
"""Quantidade de linhas removidas por tabela."""
|
|
|
|
evidencias_visuais: int
|
|
cenas: int
|
|
|
|
@property
|
|
def total(self) -> int:
|
|
"""Total de linhas removidas em todas as tabelas."""
|
|
return self.evidencias_visuais + self.cenas
|
|
|
|
|
|
class LimpadorDeDuplicatas:
|
|
"""
|
|
Remove duplicatas exatas de evidências visuais e cenas.
|
|
|
|
Atributos:
|
|
conexao: Conexão SQLite já aberta e com o esquema aplicado.
|
|
"""
|
|
|
|
def __init__(self, conexao: sqlite3.Connection) -> None:
|
|
"""
|
|
Inicializa o limpador sobre uma conexão existente.
|
|
|
|
Parâmetros:
|
|
conexao: Conexão SQLite já aberta e com o esquema aplicado.
|
|
"""
|
|
self.conexao = conexao
|
|
|
|
def contar_duplicatas(self) -> ResultadoDaLimpeza:
|
|
"""
|
|
Conta quantas linhas seriam removidas, sem remover nada.
|
|
|
|
Retorna:
|
|
As contagens de duplicatas por tabela.
|
|
"""
|
|
return ResultadoDaLimpeza(
|
|
evidencias_visuais=self._contar(
|
|
"evidencias_visuais", ("video_id", "clipe_id", "tipo", "inicio", "fim", "valor"),
|
|
),
|
|
cenas=self._contar("cenas", ("video_id", "clipe_id", "inicio", "fim")),
|
|
)
|
|
|
|
def limpar(self) -> ResultadoDaLimpeza:
|
|
"""
|
|
Remove as duplicatas, mantendo a linha de menor ``id`` de cada grupo.
|
|
|
|
Retorna:
|
|
As contagens de linhas efetivamente removidas por tabela.
|
|
"""
|
|
with self.conexao:
|
|
evidencias = self._remover(
|
|
"evidencias_visuais", ("video_id", "clipe_id", "tipo", "inicio", "fim", "valor"),
|
|
)
|
|
cenas = self._remover("cenas", ("video_id", "clipe_id", "inicio", "fim"))
|
|
return ResultadoDaLimpeza(evidencias_visuais=evidencias, cenas=cenas)
|
|
|
|
def _contar(self, tabela: str, chave: tuple[str, ...]) -> int:
|
|
"""Conta linhas que não são a primeira ocorrência do seu grupo."""
|
|
colunas = ", ".join(chave)
|
|
return int(self.conexao.execute(
|
|
f"""SELECT count(*) FROM {tabela}
|
|
WHERE id NOT IN (SELECT min(id) FROM {tabela} GROUP BY {colunas})"""
|
|
).fetchone()[0])
|
|
|
|
def _remover(self, tabela: str, chave: tuple[str, ...]) -> int:
|
|
"""Apaga as linhas que não são a primeira ocorrência do seu grupo."""
|
|
colunas = ", ".join(chave)
|
|
cursor = self.conexao.execute(
|
|
f"""DELETE FROM {tabela}
|
|
WHERE id NOT IN (SELECT min(id) FROM {tabela} GROUP BY {colunas})"""
|
|
)
|
|
return cursor.rowcount
|