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:
@@ -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",
|
||||
|
||||
@@ -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"]
|
||||
@@ -0,0 +1,40 @@
|
||||
"""Entrada de linha de comando para registrar uma edição concluída no banco."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
CAMINHO_DO_CODIGO = Path(__file__).resolve().parent.parent
|
||||
if str(CAMINHO_DO_CODIGO) not in sys.path:
|
||||
sys.path.insert(0, str(CAMINHO_DO_CODIGO))
|
||||
|
||||
from engine.persistencia.conexao import abrir_banco
|
||||
from engine.persistencia.repositorio_de_edicoes import RepositorioDeEdicoes, edicao_de_dict
|
||||
|
||||
|
||||
def registrar(caminho_do_pedido: Path, caminho_do_banco: Path) -> int:
|
||||
"""Lê o pedido do painel e registra a configuração editorial."""
|
||||
dados = json.loads(caminho_do_pedido.read_text(encoding="utf-8"))
|
||||
repositorio = RepositorioDeEdicoes(abrir_banco(caminho_do_banco))
|
||||
return repositorio.registrar(edicao_de_dict(dados))
|
||||
|
||||
|
||||
def principal() -> None:
|
||||
"""Executa o registro pela linha de comando."""
|
||||
argumentos = argparse.ArgumentParser()
|
||||
argumentos.add_argument("pedido", type=Path)
|
||||
argumentos.add_argument("--banco", type=Path, default=Path(".jhonny/analises.db"))
|
||||
opcoes = argumentos.parse_args()
|
||||
try:
|
||||
identificador = registrar(opcoes.pedido, opcoes.banco)
|
||||
print(json.dumps({"ok": True, "edicao_id": identificador}))
|
||||
except (OSError, ValueError) as erro:
|
||||
print(json.dumps({"ok": False, "erro": str(erro)}, ensure_ascii=False))
|
||||
raise SystemExit(1) from erro
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
principal()
|
||||
@@ -8,7 +8,9 @@ from pathlib import Path
|
||||
from engine.dominio import Clipe, Faixa, IntervaloDeTempo, Timeline
|
||||
from engine.integracoes.midia.extracao_de_metadados import MetadadosDoArquivo
|
||||
from engine.persistencia import (
|
||||
EdicaoDeVideo,
|
||||
RepositorioDeAnalisesSQLite,
|
||||
RepositorioDeEdicoes,
|
||||
RepositorioDeRetakesSQLite,
|
||||
RepositorioDeTimelineSQLite,
|
||||
abrir_banco,
|
||||
@@ -23,6 +25,7 @@ TABELAS_ESPERADAS = {
|
||||
"segmentos_de_transcricao", "palavras_de_transcricao",
|
||||
"evidencias_visuais", "cenas", "grupos_de_retake",
|
||||
"tomadas_de_retake", "evidencias_de_retake",
|
||||
"edicoes_de_video",
|
||||
}
|
||||
|
||||
|
||||
@@ -129,6 +132,27 @@ class TesteRepositorioDeAnalisesSQLite(unittest.TestCase):
|
||||
self.assertEqual(linha["largura"], 1920)
|
||||
self.assertEqual(linha["hash_do_conteudo"], "hash123")
|
||||
|
||||
|
||||
class TesteRepositorioDeEdicoes(unittest.TestCase):
|
||||
def test_registrar_configuracao_editorial_preserva_dados(self):
|
||||
with tempfile.TemporaryDirectory() as pasta:
|
||||
conexao = abrir_banco(Path(pasta) / "analises.db")
|
||||
repositorio = RepositorioDeEdicoes(conexao)
|
||||
identificador = repositorio.registrar(EdicaoDeVideo(
|
||||
video_id="sequencia-1",
|
||||
sequencia="Depoimento",
|
||||
origem="/videos/depoimento.mp4",
|
||||
tipo_de_video="depoimento",
|
||||
configuracao={"legendas": {"habilitadas": True}},
|
||||
))
|
||||
linha = conexao.execute(
|
||||
"SELECT * FROM edicoes_de_video WHERE id = ?", (identificador,),
|
||||
).fetchone()
|
||||
|
||||
self.assertEqual(linha["video_id"], "sequencia-1")
|
||||
self.assertEqual(linha["tipo_de_video"], "depoimento")
|
||||
self.assertIn("habilitadas", linha["configuracao"])
|
||||
|
||||
def test_registrar_e_carregar_transcricoes_com_palavras(self):
|
||||
with tempfile.TemporaryDirectory() as pasta:
|
||||
repositorio = RepositorioDeAnalisesSQLite(Path(pasta) / "analises.db")
|
||||
|
||||
Reference in New Issue
Block a user