Spaces:
Paused
Paused
| # app.py | |
| # | |
| # Versão 18.1.0 (Interface Alinhada com a Arquitetura V2) | |
| # | |
| # - A chamada de evento da UI agora aciona o novo pipeline do Composer2D. | |
| # - A função de processamento de stream (`process_chat_stream`) foi corrigida | |
| # para ser compatível com o objeto `GenerationState`, resolvendo o AttributeError. | |
| # - A extração de dados para a galeria e o vídeo foi atualizada para ler a | |
| # nova estrutura do DNA (`storyboard_producao`). | |
| import gradio as gr | |
| import yaml | |
| import logging | |
| import os | |
| import sys | |
| import time | |
| from PIL import Image | |
| # --- 1. CONFIGURAÇÃO DE LOGGING E TEMAS --- | |
| logging.basicConfig(level=logging.INFO, format='%(message)s') | |
| logger = logging.getLogger(__name__) | |
| cinematic_theme = gr.themes.Base(primary_hue=gr.themes.colors.purple).set(body_background_fill="#111827", body_text_color="#D1D5DB", button_primary_background_fill="linear-gradient(90deg, #6D28D9, #4F46E5)", block_background_fill="#1F2937", block_border_width="1px", block_border_color="#374151") | |
| # --- 2. INICIALIZAÇÃO CRÍTICA DO FRAMEWORK --- | |
| try: | |
| import aduc_framework | |
| # Importa os tipos necessários para a UI | |
| from aduc_framework.types import PreProductionParams, ProductionParams | |
| with open("config.yaml", 'r') as f: | |
| config = yaml.safe_load(f) | |
| WORKSPACE_DIR = config['application']['workspace_dir'] | |
| # Cria a instância global do ADUC | |
| aduc = aduc_framework.create_aduc_instance(workspace_root=WORKSPACE_DIR) | |
| logger.info("Framework ADUC e interface Gradio inicializados com sucesso.") | |
| except Exception as e: | |
| logger.critical(f"ERRO CRÍTICO NA INICIALIZAÇÃO DO ADUC FRAMEWORK: {e}", exc_info=True) | |
| with gr.Blocks(theme=cinematic_theme) as demo_error: | |
| gr.Markdown("# ERRO CRÍTICO NA INICIALIZAÇÃO") | |
| gr.Markdown("Não foi possível iniciar o Aduc Framework. A aplicação não pode continuar. Verifique os logs.") | |
| gr.Textbox(value=str(e), label="Detalhes do Erro", lines=10) | |
| demo_error.launch() | |
| sys.exit(1) | |
| # --- 3. FUNÇÕES WRAPPER E LÓGICA DA UI --- | |
| def chat_beautifier(role): | |
| # (Função auxiliar para deixar os nomes dos especialistas bonitos no chat) | |
| if "Composer2D" in role: return "Arquiteto Narrativo" | |
| if "Neura_Link" in role: return "Interface Neural" | |
| if "Planner5D" in role: return "Diretor de Set" | |
| return "Sistema" | |
| def process_chat_stream(generator, initial_chat_history=[]): | |
| """ | |
| Processa um gerador que produz objetos GenerationState e formata | |
| o histórico de chat e outros dados para a UI. | |
| """ | |
| chatbot_display_history = initial_chat_history.copy() | |
| fully_displayed_message_count = len(initial_chat_history) | |
| for dna_state in generator: # O gerador agora produz objetos 'GenerationState' | |
| # --- CORREÇÃO DE `AttributeError` --- | |
| # Acessamos os atributos do objeto Pydantic diretamente | |
| backend_chat_history = dna_state.chat_history | |
| gallery_images = [] | |
| if dna_state.storyboard_producao: # Lendo da nova estrutura V2 | |
| for scene in dna_state.storyboard_producao: | |
| gallery_images.extend([kf.caminho_pixel for kf in scene.keyframes]) | |
| elif dna_state.scenes: # Fallback para a estrutura legada | |
| for scene in dna_state.scenes: | |
| gallery_images.extend([kf.caminho_pixel for kf in scene.keyframes]) | |
| final_video_path = dna_state.caminho_filme_final_bruto or dna_state.caminho_filme_final_masterizado | |
| dna_json = dna_state.model_dump() # Converte o estado atual para JSON para exibição | |
| # ------------------------------------ | |
| # Lógica para "digitar" a mensagem | |
| while len(backend_chat_history) > fully_displayed_message_count: | |
| new_message_obj = backend_chat_history[fully_displayed_message_count] | |
| role = chat_beautifier(new_message_obj.get('role', 'Sistema')) | |
| content_to_type = new_message_obj.get('content', '') | |
| is_ai_message = "Sistema" not in role | |
| chatbot_display_history.append({"role": "assistant" if is_ai_message else "user", "content": ""}) | |
| full_typed_message = "" | |
| for char in f"**{role}:** {content_to_type}": | |
| full_typed_message += char | |
| chatbot_display_history[-1]["content"] = full_typed_message + "▌" | |
| yield chatbot_display_history, gr.update(value=gallery_images), gr.update(value=dna_json), gr.update(value=final_video_path) | |
| time.sleep(0.005) | |
| chatbot_display_history[-1]["content"] = full_typed_message | |
| fully_displayed_message_count += 1 | |
| # Garante que a UI seja atualizada mesmo que não haja novas mensagens de chat | |
| yield chatbot_display_history, gr.update(value=gallery_images), gr.update(value=dna_json), gr.update(value=final_video_path) | |
| def run_story_and_keyframes_wrapper(project_name, prompt, num_scenes, ref_files, duration_per_fragment): | |
| """ | |
| Este wrapper inicia o pipeline de PRÉ-PRODUÇÃO V2. | |
| """ | |
| if not project_name or not project_name.strip(): | |
| raise gr.Error("Por favor, forneça um nome para o projeto.") | |
| aduc.load_project(project_name.strip()) | |
| if not ref_files: raise gr.Error("Por favor, forneça pelo menos uma imagem de referência.") | |
| ref_paths = [aduc.process_image_for_story(f.name, f"ref_{i}.jpg") for i, f in enumerate(ref_files)] | |
| params = PreProductionParams( | |
| prompt=prompt, | |
| num_scenes=int(num_scenes), | |
| ref_paths=ref_paths, | |
| duration_per_fragment=duration_per_fragment | |
| ) | |
| # Chama a função V2 do AducSdr, que espera `params` e retorna um gerador de `GenerationState` | |
| generator = aduc.task_run_story_and_keyframes(params) | |
| final_state_json = {} | |
| for update in process_chat_stream(generator): | |
| yield update | |
| if update[2] is not gr.skip(): | |
| final_state_json = update[2] # O terceiro item é o DNA em formato JSON | |
| return final_state_json | |
| def run_production_wrapper(project_name, state_dict, trim, handler, dest, guidance, stg, steps): | |
| """ | |
| Este wrapper inicia o pipeline de PRODUÇÃO V2. | |
| """ | |
| if not project_name or not project_name.strip(): | |
| raise gr.Error("O nome do projeto parece ter sido perdido.") | |
| aduc.load_project(project_name.strip()) | |
| params = ProductionParams(trim_percent=int(trim), handler_strength=handler, destination_convergence_strength=dest, guidance_scale=guidance, stg_scale=stg, inference_steps=int(steps)) | |
| # A nova função de produção do AducSdr pode precisar ser ajustada para receber os params | |
| # ou lê-los diretamente do DNA que já foi atualizado na pré-produção. | |
| # Por enquanto, mantemos a chamada. | |
| prod_generator = aduc.task_produce_movie(params) | |
| # O `state_dict` já é um dicionário, então podemos extrair o histórico de chat dele | |
| initial_chat = state_dict.get("chat_history", []) | |
| final_state_json = {} | |
| for update in process_chat_stream(prod_generator, initial_chat_history=initial_chat): | |
| yield update | |
| if update[2] is not gr.skip(): | |
| final_state_json = update[2] | |
| return final_state_json | |
| # --- 4. DEFINIÇÃO DA UI --- | |
| with gr.Blocks(theme=cinematic_theme, css="style.css") as demo: | |
| generation_state_holder = gr.State({}) | |
| gr.Markdown("<h1>ADUC-SDR 🎬 - O Diretor de Cinema IA</h1>") | |
| with gr.Accordion("Configurações do Projeto", open=True): | |
| project_name_input = gr.Textbox(label="Nome do Projeto", value="Meu_Filme_01", info="O progresso será salvo em uma pasta com este nome.") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| with gr.Accordion("Etapa 1: Pré-Produção (Roteiro e Storyboard)", open=True): | |
| prompt_input = gr.Textbox(label="Ideia Geral do Filme", value="Um robô solitário explora as ruínas de uma cidade coberta pela natureza.") | |
| ref_image_input = gr.File(label="Imagens de Referência", file_count="multiple", file_types=["image"]) | |
| with gr.Row(): | |
| num_scenes_slider = gr.Slider(minimum=1, maximum=10, value=3, step=1, label="Número de Cenas") | |
| duration_per_fragment_slider = gr.Slider(label="Duração de cada Ato (s)", minimum=2.0, maximum=10.0, value=5.0, step=0.1) | |
| start_pre_prod_button = gr.Button("1. Gerar Roteiro e Storyboard", variant="primary") | |
| with gr.Accordion("Etapa 2: Produção do Vídeo", open=False, visible=False) as step2_accordion: | |
| trim_percent_slider = gr.Slider(minimum=10, maximum=90, value=50, step=5, label="Poda Causal (%)") | |
| handler_strength_slider = gr.Slider(label="Força do Déjà-Vu", minimum=0.0, maximum=1.0, value=0.5, step=0.05) | |
| dest_strength_slider = gr.Slider(label="Força da Âncora Final", minimum=0.0, maximum=1.0, value=0.75, step=0.05) | |
| guidance_scale_slider = gr.Slider(minimum=1.0, maximum=10.0, value=2.0, step=0.1, label="Escala de Orientação") | |
| stg_scale_slider = gr.Slider(minimum=0.0, maximum=1.0, value=0.025, step=0.005, label="Escala STG") | |
| inference_steps_slider = gr.Slider(minimum=10, maximum=50, value=20, step=1, label="Passos de Inferência") | |
| produce_original_button = gr.Button("2. Produzir Vídeo", variant="primary", interactive=False) | |
| with gr.Column(scale=3): | |
| final_video_output = gr.Video(label="Último Clipe Gerado / Filme Final", interactive=False) | |
| chat_history_chatbot = gr.Chatbot(label="Diário da Produção", height=600, type='messages') | |
| keyframe_gallery = gr.Gallery(label="Keyframes Gerados", object_fit="contain", height="auto") | |
| with gr.Accordion("🧬 DNA Digital (Estado do Projeto)", open=False): | |
| dna_display = gr.JSON() | |
| # --- 5. CONEXÕES DE EVENTOS --- | |
| start_pre_prod_button.click( | |
| fn=lambda: gr.update(interactive=False), | |
| outputs=[start_pre_prod_button] | |
| ).then( | |
| fn=run_story_and_keyframes_wrapper, | |
| inputs=[project_name_input, prompt_input, num_scenes_slider, ref_image_input, duration_per_fragment_slider], | |
| outputs=[chat_history_chatbot, keyframe_gallery, dna_display, final_video_output] | |
| ).then( | |
| fn=lambda data: (data, gr.update(visible=True, open=True), gr.update(interactive=True)), | |
| inputs=dna_display, | |
| outputs=[generation_state_holder, step2_accordion, produce_original_button] | |
| ) | |
| produce_original_button.click( | |
| fn=lambda: gr.update(interactive=False), | |
| outputs=[produce_original_button] | |
| ).then( | |
| fn=run_production_wrapper, | |
| inputs=[ | |
| project_name_input, | |
| generation_state_holder, | |
| trim_percent_slider, | |
| handler_strength_slider, | |
| dest_strength_slider, | |
| guidance_scale_slider, | |
| stg_scale_slider, | |
| inference_steps_slider | |
| ], | |
| outputs=[chat_history_chatbot, keyframe_gallery, dna_display, final_video_output] | |
| ) | |
| if __name__ == "__main__": | |
| os.makedirs(WORKSPACE_DIR, exist_ok=True) | |
| logger.info("Aplicação Gradio pronta. Lançando interface...") | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=int(os.getenv("PORT", "7860")), | |
| ) |