Aduc-sdr-2_5 / app.py
x2XcarleX2x's picture
Rename app1.py to app.py
bd22950 verified
# 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")),
)