feat: corrigida a reprodução do trecho de áudio do falante na tela

- corrigida a reprodução do trecho de áudio do falante na tela Refinar usando os tempos da origem do WAV.
- alterado o passo de geração de JSON para concluir a edição e salvar a configuração editorial no banco.

Resumo:
- 9 arquivos alterados
- 3 novos
- 6 modificados
- 0 removidos

 6 files changed, 136 insertions(+), 109 deletions(-)

Arquivos:
  - .jhonny/analises.db
  - code/cep-plugin/index.html
  - code/cep-plugin/main.js
  - code/engine/persistencia/__init__.py
  - code/engine/persistencia/esquema.py
  - code/engine/testes/test_persistencia_sqlite.py
  - code/engine/persistencia/repositorio_de_edicoes.py
  - code/engine/registrar_edicao.py
  - code/tests/cep-refinar-preview.test.ts
This commit is contained in:
João Henrique
2026-09-10 10:28:41 -04:00
parent 25800b2427
commit b01482d0e7
9 changed files with 278 additions and 109 deletions
+5
View File
@@ -25,6 +25,7 @@ from .repositorio_de_planos import (
RepositorioDePlanos,
plano_de_dict,
)
from .repositorio_de_edicoes import EdicaoDeVideo, ErroDeEdicao, RepositorioDeEdicoes, edicao_de_dict
from .repositorio_de_analises_sqlite import RepositorioDeAnalisesSQLite
from .repositorio_de_retakes_sqlite import RepositorioDeRetakesSQLite
from .repositorio_de_timeline_sqlite import RepositorioDeTimelineSQLite
@@ -37,6 +38,8 @@ __all__ = [
"ErroDeBusca",
"ErroDeIngestaoDeVoz",
"ErroDePlano",
"ErroDeEdicao",
"EdicaoDeVideo",
"IndexadorSemantico",
"IngestorDeVozNoBanco",
"LeitorDeDadosParaIA",
@@ -44,10 +47,12 @@ __all__ = [
"PlanoDeEdicao",
"RepositorioDeEnunciados",
"RepositorioDePlanos",
"RepositorioDeEdicoes",
"ResultadoDeBusca",
"abrir_banco",
"criar_esquema",
"plano_de_dict",
"edicao_de_dict",
"reconstruir_indice_de_busca",
"RepositorioDeAnalisesSQLite",
"RepositorioDeRetakesSQLite",
+14
View File
@@ -285,6 +285,20 @@ CREATE TABLE IF NOT EXISTS aplicacoes_do_plano (
);
CREATE INDEX IF NOT EXISTS idx_aplicacoes_plano ON aplicacoes_do_plano(plano_id);
-- Configuração editorial escolhida no painel, antes de existir um plano da IA.
-- A análise continua normalizada nas tabelas do Scanner; esta tabela guarda
-- somente a decisão do usuário e suas opções para a edição.
CREATE TABLE IF NOT EXISTS edicoes_de_video (
id INTEGER PRIMARY KEY AUTOINCREMENT,
video_id TEXT NOT NULL REFERENCES videos(id) ON DELETE CASCADE,
sequencia TEXT NOT NULL,
origem TEXT,
tipo_de_video TEXT NOT NULL,
configuracao TEXT NOT NULL,
concluida_em TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
);
CREATE INDEX IF NOT EXISTS idx_edicoes_video ON edicoes_de_video(video_id, concluida_em);
-- ---------------------------------------------------------------------------
-- Leituras achatadas
-- ---------------------------------------------------------------------------
@@ -0,0 +1,83 @@
"""Persistência da configuração editorial escolhida no painel CEP.
Este módulo registra a conclusão da seleção de um tipo de vídeo e suas opções.
Ele não executa cortes, não gera o plano da IA e não duplica a análise do
Scanner; apenas cria o vínculo persistido entre a análise e a edição desejada.
"""
from __future__ import annotations
import json
import sqlite3
from dataclasses import dataclass
class ErroDeEdicao(ValueError):
"""Configuração editorial inválida para persistência."""
@dataclass(frozen=True)
class EdicaoDeVideo:
"""Representa a configuração editorial concluída para um vídeo."""
video_id: str
sequencia: str
tipo_de_video: str
configuracao: dict[str, object]
origem: str | None = None
class RepositorioDeEdicoes:
"""Grava configurações editoriais sem alterar os dados da análise."""
def __init__(self, conexao: sqlite3.Connection) -> None:
"""Inicializa o repositório com uma conexão SQLite preparada."""
self.conexao = conexao
def registrar(self, edicao: EdicaoDeVideo) -> int:
"""Registra uma edição concluída e devolve seu identificador."""
if not edicao.video_id.strip():
raise ErroDeEdicao("A edição precisa indicar o vídeo da análise.")
if not edicao.sequencia.strip():
raise ErroDeEdicao("A edição precisa indicar a sequência.")
if not edicao.tipo_de_video.strip():
raise ErroDeEdicao("A edição precisa indicar o tipo de vídeo.")
if not edicao.configuracao:
raise ErroDeEdicao("A edição precisa ter uma configuração.")
with self.conexao:
self.conexao.execute(
"INSERT OR IGNORE INTO videos (id, nome) VALUES (?, ?)",
(edicao.video_id, edicao.sequencia),
)
cursor = self.conexao.execute(
"""INSERT INTO edicoes_de_video
(video_id, sequencia, origem, tipo_de_video, configuracao)
VALUES (?, ?, ?, ?, ?)""",
(
edicao.video_id,
edicao.sequencia,
edicao.origem,
edicao.tipo_de_video,
json.dumps(edicao.configuracao, ensure_ascii=False),
),
)
return int(cursor.lastrowid)
def edicao_de_dict(dados: dict[str, object]) -> EdicaoDeVideo:
"""Converte o pedido externo do painel em uma entidade validável."""
try:
video_id = str(dados["video_id"])
sequencia = str(dados["sequencia"])
tipo_de_video = str(dados["tipo_de_video"])
configuracao = dados["configuracao"]
except KeyError as erro:
raise ErroDeEdicao(f"Campo obrigatório ausente: {erro.args[0]}.") from erro
if not isinstance(configuracao, dict):
raise ErroDeEdicao("O campo configuracao precisa ser um objeto.")
origem = dados.get("origem")
return EdicaoDeVideo(video_id, sequencia, tipo_de_video, configuracao, str(origem) if origem else None)
__all__ = ["EdicaoDeVideo", "ErroDeEdicao", "RepositorioDeEdicoes", "edicao_de_dict"]