"""Modelo de domínio do plano de edição. Representa, em memória, o contrato JSON produzido pelas skills de seleção de trechos (``kind``/``start``/``end``/``params``/``reason``) já validado e convertido para tipos internos. Nenhuma classe aqui conhece o Premiere nem o formato de arquivo — isso é responsabilidade de ``leitura`` e ``escrita``. """ from __future__ import annotations from dataclasses import dataclass, field from enum import Enum from typing import Any class TipoDeAcao(str, Enum): """Os quatro tipos de ação que o plano de edição pode descrever.""" CORTE = "cut" ZOOM = "zoom" TEXTO = "text" MARCADOR = "marker" @dataclass(frozen=True) class AcaoDeEdicao: """Uma decisão de edição sobre um intervalo do arquivo de origem. ``inicio`` e ``fim`` são sempre segundos na mídia original — nunca na timeline já cortada. ``motivo`` é obrigatório: é o texto que o editor lê para decidir se aceita a decisão. """ tipo: TipoDeAcao inicio: float fim: float motivo: str parametros: dict[str, Any] = field(default_factory=dict) def __post_init__(self) -> None: if self.inicio < 0: raise ValueError(f"Ação inválida: início {self.inicio}s não pode ser negativo.") if self.fim <= self.inicio: raise ValueError( f"Ação inválida: fim ({self.fim}s) deve ser maior que início ({self.inicio}s)." ) if not self.motivo.strip(): raise ValueError("Ação inválida: motivo não pode ser vazio.") @property def duracao(self) -> float: """Duração da ação em segundos, sempre positiva.""" return self.fim - self.inicio @dataclass(frozen=True) class PlanoDeEdicao: """Conjunto ordenado de ações a aplicar sobre um arquivo de origem.""" arquivo_de_origem: str acoes: tuple[AcaoDeEdicao, ...] def __post_init__(self) -> None: if not self.arquivo_de_origem.strip(): raise ValueError("Plano de edição inválido: arquivo de origem não informado.") def acoes_do_tipo(self, tipo: TipoDeAcao) -> tuple[AcaoDeEdicao, ...]: """Devolve, na ordem original, só as ações do tipo informado.""" return tuple(acao for acao in self.acoes if acao.tipo is tipo)