"""Persistência SQLite de metadados de arquivo, transcrição e evidências visuais. Complementa ``RepositorioDeRetakesSQLite`` gravando o restante do que o ``ContextoDeAnalise`` do pipeline produz e que hoje só vive em memória (metadados técnicos) ou em cache JSON por arquivo (transcrição). """ from __future__ import annotations import json import sqlite3 from pathlib import Path from typing import Iterable from ..integracoes.midia.extracao_de_metadados import MetadadosDoArquivo from ..scanner.modelos import Cena, EvidenciaVisual, PalavraDeTranscricao, SegmentoDeTranscricao from ..scanner.transcricao_da_timeline import TranscricaoDoClipe from .conexao import abrir_banco class RepositorioDeAnalisesSQLite: """Grava metadados de arquivo, transcrição e evidências/cenas visuais.""" def __init__(self, banco: str | Path | sqlite3.Connection = ".jhonny/analises.db") -> None: self.conexao = banco if isinstance(banco, sqlite3.Connection) else abrir_banco(banco) def _garantir_video(self, video_id: str) -> None: self.conexao.execute("INSERT OR IGNORE INTO videos (id) VALUES (?)", (video_id,)) def registrar_metadados_de_arquivo( self, video_id: str, metadados: MetadadosDoArquivo, hash_do_conteudo: str | None = None, ) -> None: with self.conexao: self._garantir_video(video_id) self.conexao.execute( """INSERT INTO arquivos (video_id, caminho, formato, duracao, codec_de_video, codec_de_audio, largura, altura, taxa_de_quadros, canais_de_audio, taxa_de_amostragem, tamanho_em_bytes, orientacao, timecode, hash_do_conteudo) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (video_id, caminho) DO UPDATE SET formato = excluded.formato, duracao = excluded.duracao, codec_de_video = excluded.codec_de_video, codec_de_audio = excluded.codec_de_audio, largura = excluded.largura, altura = excluded.altura, taxa_de_quadros = excluded.taxa_de_quadros, canais_de_audio = excluded.canais_de_audio, taxa_de_amostragem = excluded.taxa_de_amostragem, tamanho_em_bytes = excluded.tamanho_em_bytes, orientacao = excluded.orientacao, timecode = excluded.timecode, hash_do_conteudo = excluded.hash_do_conteudo""", (video_id, str(metadados.caminho), metadados.formato, metadados.duracao, metadados.codec_de_video, metadados.codec_de_audio, metadados.largura, metadados.altura, metadados.taxa_de_quadros, metadados.canais_de_audio, metadados.taxa_de_amostragem, metadados.tamanho_em_bytes, metadados.orientacao, metadados.timecode, hash_do_conteudo), ) def registrar_transcricoes( self, video_id: str, transcricoes: Iterable[TranscricaoDoClipe], ) -> None: with self.conexao: self._garantir_video(video_id) for transcricao in transcricoes: for segmento in transcricao.segmentos: self._inserir_segmento(video_id, transcricao.identificador_do_clipe, segmento) def _inserir_segmento( self, video_id: str, clipe_id: str, segmento: SegmentoDeTranscricao, ) -> int: cursor = self.conexao.execute( """INSERT INTO segmentos_de_transcricao (video_id, clipe_id, inicio, fim, texto, confianca, falante, voz_aparente, confianca_voz, emocao, confianca_emocao, caracteristicas_acusticas) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", (video_id, clipe_id, segmento.inicio, segmento.fim, segmento.texto, segmento.confianca, segmento.falante, segmento.voz_aparente, segmento.confianca_voz, segmento.emocao, segmento.confianca_emocao, json.dumps(segmento.caracteristicas_acusticas, ensure_ascii=False)), ) segmento_id = cursor.lastrowid for ordem, palavra in enumerate(segmento.palavras): self.conexao.execute( """INSERT INTO palavras_de_transcricao (segmento_id, ordem, texto, inicio, fim, confianca, falante) VALUES (?, ?, ?, ?, ?, ?, ?)""", (segmento_id, ordem, palavra.texto, palavra.inicio, palavra.fim, palavra.confianca, palavra.falante), ) return segmento_id def carregar_transcricoes(self, video_id: str, clipe_id: str) -> list[SegmentoDeTranscricao]: segmentos: list[SegmentoDeTranscricao] = [] for linha in self.conexao.execute( """SELECT * FROM segmentos_de_transcricao WHERE video_id = ? AND clipe_id = ? ORDER BY inicio""", (video_id, clipe_id), ).fetchall(): palavras = tuple( PalavraDeTranscricao( texto=item["texto"], inicio=item["inicio"], fim=item["fim"], confianca=item["confianca"], falante=item["falante"], ) for item in self.conexao.execute( "SELECT * FROM palavras_de_transcricao WHERE segmento_id = ? ORDER BY ordem", (linha["id"],), ).fetchall() ) segmentos.append(SegmentoDeTranscricao( inicio=linha["inicio"], fim=linha["fim"], texto=linha["texto"], confianca=linha["confianca"], palavras=palavras, emocao=linha["emocao"], confianca_emocao=linha["confianca_emocao"], caracteristicas_acusticas=json.loads(linha["caracteristicas_acusticas"] or "{}"), falante=linha["falante"], voz_aparente=linha["voz_aparente"], confianca_voz=linha["confianca_voz"], )) return segmentos def registrar_evidencias_visuais( self, video_id: str, clipe_id: str, evidencias: Iterable[EvidenciaVisual], ) -> None: with self.conexao: self._garantir_video(video_id) for evidencia in evidencias: self.conexao.execute( """INSERT INTO evidencias_visuais (video_id, clipe_id, tipo, inicio, fim, valor, confianca, provider, modelo) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""", (video_id, clipe_id, evidencia.tipo, evidencia.inicio, evidencia.fim, json.dumps(evidencia.valor, ensure_ascii=False), evidencia.confianca, evidencia.provider, evidencia.modelo), ) def registrar_cenas( self, video_id: str, cenas: Iterable[Cena], clipe_id: str | None = None, ) -> None: with self.conexao: self._garantir_video(video_id) for cena in cenas: self.conexao.execute( """INSERT INTO cenas (video_id, clipe_id, inicio, fim, confianca, referencias) VALUES (?, ?, ?, ?, ?, ?)""", (video_id, clipe_id, cena.inicio, cena.fim, cena.confianca, json.dumps(list(cena.referencias))), )