"""Perfil declarativo que liga a configuração do painel à leitura visual.""" from dataclasses import dataclass from typing import Any RECURSOS_VISION = frozenset(( "faces", "qualidade_facial", "pessoas", "pose", "maos", "ocr", "categorias", "estetica", "codigos", "horizonte", "retangulos", "contornos", "segmentacao_de_pessoas", )) # Sensibilidades aceitas para a análise visual auxiliar e o limiar do # detector de cortes (PySceneDetect). Valores MENORES de limiar são mais # sensíveis — detectam mais cortes. O default segue o PySceneDetect (27). SENSIBILIDADES_VISUAIS: dict[str, float] = { "muito_baixa": 45.0, "baixa": 35.0, "media": 27.0, "alta": 20.0, "muito_alta": 14.0, } @dataclass(frozen=True) class ConfiguracaoVisualDoScanner: """Interface curta para uma execução de leitura visual da timeline.""" intervalo_em_segundos: float = 1.0 resolucao_maxima: int = 1080 faixas_de_video: tuple[int, ...] = () faixas_de_audio: tuple[int, ...] = () recursos_vision: frozenset[str] = RECURSOS_VISION detectar_cenas: bool = True analisar_continuidade: bool = True preparar_reenquadramento: bool = False interpretar_cena: bool = True # Análise visual auxiliar (PySceneDetect) — desligada por padrão. usar_analise_visual: bool = False detectar_cortes: bool = True sensibilidade: str = "media" def __post_init__(self) -> None: if not 0.04 <= self.intervalo_em_segundos <= 60: raise ValueError("intervalo_em_segundos deve estar entre 0,04 e 60.") if self.resolucao_maxima != 0 and not 240 <= self.resolucao_maxima <= 8640: raise ValueError("resolucao_maxima deve ser 0 (original) ou estar entre 240 e 8640.") desconhecidos = self.recursos_vision - RECURSOS_VISION if desconhecidos: raise ValueError(f"Recursos Vision desconhecidos: {', '.join(sorted(desconhecidos))}") if any(indice < 0 for indice in self.faixas_de_video + self.faixas_de_audio): raise ValueError("Índices de faixa não podem ser negativos.") if self.sensibilidade not in SENSIBILIDADES_VISUAIS: raise ValueError( f"Sensibilidade inválida: {self.sensibilidade}. " f"Use um de {', '.join(SENSIBILIDADES_VISUAIS)}.") @property def limiar_de_corte(self) -> float: """Limiar do ContentDetector para a sensibilidade configurada.""" return SENSIBILIDADES_VISUAIS[self.sensibilidade] @classmethod def de_dict(cls, dados: dict[str, Any]) -> "ConfiguracaoVisualDoScanner": """Lê o mesmo JSON produzido pelo painel, sem vazar detalhes de adapters.""" return cls( intervalo_em_segundos=float(dados.get("intervalo_em_segundos", 1)), resolucao_maxima=int(dados.get("resolucao_maxima", 1080)), faixas_de_video=tuple(sorted(set(int(item) for item in dados.get("faixas_de_video", ())))), faixas_de_audio=tuple(sorted(set(int(item) for item in dados.get("faixas_de_audio", ())))), recursos_vision=frozenset(dados.get("recursos_vision", RECURSOS_VISION)), detectar_cenas=bool(dados.get("detectar_cenas", True)), analisar_continuidade=bool(dados.get("analisar_continuidade", True)), preparar_reenquadramento=bool(dados.get("preparar_reenquadramento", False)), interpretar_cena=bool(dados.get("interpretar_cena", True)), usar_analise_visual=bool(dados.get("usar_analise_visual", False)), detectar_cortes=bool(dados.get("detectar_cortes", True)), sensibilidade=str(dados.get("sensibilidade", "media")), ) def para_dict(self) -> dict[str, Any]: return { "versao": 1, "intervalo_em_segundos": self.intervalo_em_segundos, "resolucao_maxima": self.resolucao_maxima, "faixas_de_video": list(self.faixas_de_video), "faixas_de_audio": list(self.faixas_de_audio), "recursos_vision": sorted(self.recursos_vision), "detectar_cenas": self.detectar_cenas, "analisar_continuidade": self.analisar_continuidade, "preparar_reenquadramento": self.preparar_reenquadramento, "interpretar_cena": self.interpretar_cena, "usar_analise_visual": self.usar_analise_visual, "detectar_cortes": self.detectar_cortes, "sensibilidade": self.sensibilidade, }