# aduc_framework/engineers/deformes2D_thinker.py # # Copyright (C) 4 de Agosto de 2025 Carlos Rodrigues dos Santos # # Versão 4.0.0 (Dual-Role Autonomous Director & Cinematographer) # # Esta classe é o cérebro criativo do framework. Ela utiliza um LLM para # desempenhar múltiplos papéis: # 1. Roteirista: Cria a lista inicial de atos. # 2. Diretor Autônomo: Gerencia a produção de keyframes, com poder de # avaliar, corrigir e improvisar o roteiro. # 3. Cineasta: Cria os prompts de movimento que conectam os keyframes. import logging from pathlib import Path from PIL import Image import gradio as gr from typing import List, Dict, Any from ..managers.gemini_manager import gemini_manager_singleton logger = logging.getLogger(__name__) class Deformes2DThinker: """ O especialista cognitivo que atua como a inteligência criativa central, desempenhando os papéis de Roteirista, Diretor Autônomo e Cineasta. """ def _read_prompt_template(self, filename: str) -> str: """Lê um arquivo de template de prompt do diretório 'prompts'.""" try: prompts_dir = Path(__file__).resolve().parent.parent / "prompts" with open(prompts_dir / filename, "r", encoding="utf-8") as f: return f.read() except FileNotFoundError: raise gr.Error(f"Arquivo de template de prompt não encontrado: {filename}") # --- PAPEL 1: DIRETOR AUTÔNOMO (Para Deformes3D) --- def get_directorial_decision(self, context: Dict[str, Any]) -> Dict[str, Any]: """ Função principal do Diretor. Analisa o estado da produção e retorna a próxima ação (avançar, corrigir, improvisar) para a criação de KEYFRAMES. """ try: template = self._read_prompt_template("autonomous_director_prompt.txt") keyframes_str = "\n".join([f"- ID {kf['id']}: {kf['prompt_keyframe']}" for kf in context.get('keyframes_gerados', [])]) script_str = "\n".join([f"- Ato {i+1}: {ato}" for i, ato in enumerate(context.get('roteiro_completo', []))]) formatted_prompt = template.format( prompt_geral=context.get('prompt_geral', ''), midias_usuario=", ".join(context.get('midias_usuario', [])), roteiro_completo=script_str, indice_ato_atual=context.get('indice_ato_atual', 0), keyframes_gerados=keyframes_str if keyframes_str else "Nenhuma cena filmada ainda." ) prompt_parts = [formatted_prompt] keyframes_gerados = context.get('keyframes_gerados', []) if keyframes_gerados: last_keyframe_path = keyframes_gerados[-1]['caminho_pixel'] prompt_parts.append("\nÚltimo Keyframe Gerado (para sua avaliação crítica):") try: prompt_parts.append(Image.open(last_keyframe_path)) except FileNotFoundError: logger.warning(f"Não foi possível encontrar a imagem do último keyframe: {last_keyframe_path}") decision_json = gemini_manager_singleton.get_json_object(prompt_parts) return decision_json except Exception as e: logger.error(f"O Diretor Autônomo (Gemini) falhou: {e}. Acionando fallback.", exc_info=True) return self._get_fallback_decision(context) def _get_fallback_decision(self, context: Dict[str, Any]) -> Dict[str, Any]: """Gera uma decisão de 'avançar' segura em caso de falha do LLM.""" script = context.get('roteiro_completo', []) current_index = context.get('indice_ato_atual', 0) current_act_text = script[current_index] if current_index < len(script) else "cena final" return { "acao": "avancar", "justificativa": "Fallback acionado devido a erro na decisão principal da IA.", "prompt_proximo_keyframe": f"Uma cena cinematográfica para o ato: {current_act_text}", "midia_base_escolhida": context.get('midias_usuario', [''])[0], "midias_contexto_escolhidas": [], "is_cut": False } # --- PAPEL 2: CINEASTA (Para Deformes4D) --- def get_motion_decision(self, start_kf: Dict, end_kf: Dict, motion_history: str) -> str: """ Atua como Cineasta para decidir o melhor `motion_prompt` que conecta dois keyframes existentes, criando o MOVIMENTO. """ try: template = self._read_prompt_template("motion_director_prompt.txt") formatted_prompt = template.format( cena_atual_desc=start_kf.get('prompt_keyframe', 'início da cena'), cena_futura_desc=end_kf.get('prompt_keyframe', 'fim da cena'), historico_movimento=motion_history ) prompt_parts = [ formatted_prompt, "IMAGEM ATUAL:", Image.open(start_kf['caminho_pixel']), "IMAGEM FUTURA:", Image.open(end_kf['caminho_pixel']) ] motion_prompt = gemini_manager_singleton.get_raw_text(prompt_parts) return motion_prompt.strip().replace("`", "").replace("\"", "") except Exception as e: logger.error(f"O Cineasta (Gemini) falhou ao criar motion_prompt: {e}. Usando fallback.", exc_info=True) return f"Uma transição cinematográfica suave de '{start_kf.get('prompt_keyframe', 'cena anterior')}' para '{end_kf.get('prompt_keyframe', 'próxima cena')}'." # --- PAPEL 3: ROTEIRISTA (Para Orquestrador) --- def generate_storyboard(self, prompt: str, num_keyframes: int, ref_image_paths: List[str]) -> List[str]: """ Atua como Roteirista para criar o roteiro inicial (lista de atos) que servirá de base para o Diretor. """ try: template = self._read_prompt_template("unified_storyboard_prompt.txt") storyboard_prompt = template.format(user_prompt=prompt, num_fragments=num_keyframes) images = [Image.open(p) for p in ref_image_paths] prompt_parts = [storyboard_prompt] + images storyboard_data = gemini_manager_singleton.get_json_object(prompt_parts) storyboard = storyboard_data.get("scene_storyboard", []) if not storyboard or len(storyboard) != num_keyframes: logger.warning(f"Número de cenas gerado diferente do solicitado. Pedido: {num_keyframes}, Gerado: {len(storyboard)}") # Garante que a lista tenha o tamanho exato solicitado default_scene = storyboard[-1] if storyboard else "Cena de continuação." storyboard = (storyboard + [default_scene] * num_keyframes)[:num_keyframes] return storyboard except Exception as e: raise gr.Error(f"O Roteirista (Deformes2D Thinker) falhou ao criar o storyboard: {e}") # --- Singleton Instance --- deformes2d_thinker_singleton = Deformes2DThinker()