feat: definido projeto com uma sequência e múltiplos arquivos de m

- definido projeto com uma sequência e múltiplos arquivos de mídia
- criada persistência de projeto com uma sequência e seleções de faixas e clipes
- criada guia Projeto para selecionar sequência, faixas e clipes do Premiere
- adicionada gravação das seleções da guia Projeto no SQLite

Resumo:
- 10 arquivos alterados
- 4 novos
- 6 modificados
- 0 removidos

 6 files changed, 71 insertions(+), 2 deletions(-)

Arquivos:
  - .jhonny/analises.db
  - CONTEXT.md
  - code/cep-plugin/index.html
  - code/cep-plugin/main.js
  - code/engine/persistencia/__init__.py
  - code/engine/persistencia/esquema.py
  - code/cep-plugin/abas/
  - code/engine/persistencia/repositorio_de_projetos.py
  - code/engine/registrar_projeto_de_edicao.py
  - code/engine/testes/test_repositorio_de_projetos.py
This commit is contained in:
João Henrique
2026-09-10 13:22:15 -04:00
parent a9e5b5ff58
commit a4286bbd8a
10 changed files with 329 additions and 2 deletions
+4
View File
@@ -25,6 +25,7 @@ from .repositorio_de_planos import (
RepositorioDePlanos,
plano_de_dict,
)
from .repositorio_de_projetos import ErroDeProjeto, ProjetoDeEdicao, RepositorioDeProjetos
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
@@ -47,6 +48,9 @@ __all__ = [
"PlanoDeEdicao",
"RepositorioDeEnunciados",
"RepositorioDePlanos",
"RepositorioDeProjetos",
"ProjetoDeEdicao",
"ErroDeProjeto",
"RepositorioDeEdicoes",
"ResultadoDeBusca",
"abrir_banco",
+26
View File
@@ -285,6 +285,32 @@ CREATE TABLE IF NOT EXISTS aplicacoes_do_plano (
);
CREATE INDEX IF NOT EXISTS idx_aplicacoes_plano ON aplicacoes_do_plano(plano_id);
-- Um projeto representa exatamente uma sequência, mas pode selecionar vários
-- arquivos, faixas e clipes pertencentes a ela.
CREATE TABLE IF NOT EXISTS projetos_de_edicao (
id INTEGER PRIMARY KEY AUTOINCREMENT,
video_id TEXT NOT NULL REFERENCES videos(id) ON DELETE CASCADE,
nome TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'aberto',
criado_em TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
atualizado_em TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
);
CREATE INDEX IF NOT EXISTS idx_projetos_video ON projetos_de_edicao(video_id, atualizado_em);
CREATE TABLE IF NOT EXISTS faixas_do_projeto (
projeto_id INTEGER NOT NULL REFERENCES projetos_de_edicao(id) ON DELETE CASCADE,
faixa_id TEXT NOT NULL,
selecionada INTEGER NOT NULL DEFAULT 1,
PRIMARY KEY (projeto_id, faixa_id)
);
CREATE TABLE IF NOT EXISTS clipes_do_projeto (
projeto_id INTEGER NOT NULL REFERENCES projetos_de_edicao(id) ON DELETE CASCADE,
clipe_id TEXT NOT NULL,
selecionado INTEGER NOT NULL DEFAULT 1,
PRIMARY KEY (projeto_id, clipe_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.
@@ -0,0 +1,79 @@
"""Persistência dos projetos de edição e das seleções da guia Projeto."""
from __future__ import annotations
import sqlite3
from dataclasses import dataclass
class ErroDeProjeto(ValueError):
"""Projeto inválido para persistência."""
@dataclass(frozen=True)
class ProjetoDeEdicao:
"""Projeto associado a exatamente uma sequência do Premiere."""
nome: str
video_id: str
faixas: tuple[str, ...] = ()
clipes: tuple[str, ...] = ()
status: str = "aberto"
class RepositorioDeProjetos:
"""Grava e recupera projetos sem duplicar dados da timeline."""
def __init__(self, conexao: sqlite3.Connection) -> None:
"""Inicializa o repositório com uma conexão SQLite preparada."""
self.conexao = conexao
def registrar(self, projeto: ProjetoDeEdicao) -> int:
"""Cria um projeto e grava suas faixas e clipes selecionados."""
if not projeto.nome.strip():
raise ErroDeProjeto("O projeto precisa ter um nome.")
if not projeto.video_id.strip():
raise ErroDeProjeto("O projeto precisa ter uma sequência.")
with self.conexao:
cursor = self.conexao.execute(
"""INSERT INTO projetos_de_edicao (video_id, nome, status)
VALUES (?, ?, ?)""",
(projeto.video_id, projeto.nome, projeto.status),
)
projeto_id = int(cursor.lastrowid)
self.conexao.executemany(
"INSERT INTO faixas_do_projeto (projeto_id, faixa_id) VALUES (?, ?)",
[(projeto_id, faixa_id) for faixa_id in projeto.faixas],
)
self.conexao.executemany(
"INSERT INTO clipes_do_projeto (projeto_id, clipe_id) VALUES (?, ?)",
[(projeto_id, clipe_id) for clipe_id in projeto.clipes],
)
return projeto_id
def carregar(self, projeto_id: int) -> dict[str, object] | None:
"""Carrega um projeto com sua sequência, faixas e clipes selecionados."""
linha = self.conexao.execute(
"SELECT * FROM projetos_de_edicao WHERE id = ?", (projeto_id,),
).fetchone()
if linha is None:
return None
return {
"id": linha["id"], "nome": linha["nome"], "video_id": linha["video_id"],
"status": linha["status"], "faixas": [
r["faixa_id"] for r in self.conexao.execute(
"""SELECT faixa_id FROM faixas_do_projeto
WHERE projeto_id = ? AND selecionada = 1 ORDER BY rowid""",
(projeto_id,),
)
], "clipes": [
r["clipe_id"] for r in self.conexao.execute(
"""SELECT clipe_id FROM clipes_do_projeto
WHERE projeto_id = ? AND selecionado = 1 ORDER BY rowid""",
(projeto_id,),
)
],
}
__all__ = ["ErroDeProjeto", "ProjetoDeEdicao", "RepositorioDeProjetos"]