Sam commited on
Commit
77305cb
·
verified ·
1 Parent(s): 7a1f143

Update aduc_framework/orchestrator.py

Browse files
Files changed (1) hide show
  1. aduc_framework/orchestrator.py +131 -80
aduc_framework/orchestrator.py CHANGED
@@ -2,30 +2,39 @@
2
  #
3
  # Copyright (C) August 4, 2025 Carlos Rodrigues dos Santos
4
  #
5
- # Versão 6.1.1 (Delegate Storyboard to 3D Thinker)
6
  #
7
- # Esta versão atualiza o orquestrador para delegar a tarefa de geração
8
- # de storyboard ao novo especialista Deformes3DThinker, que utiliza
9
- # o Llama Scout Manager.
 
10
 
11
  import logging
12
  from typing import List, Dict, Any, Tuple, Callable, Optional, Generator
13
  from PIL import Image, ImageOps
14
  import os
15
- import subprocess
16
  import shutil
17
- from pathlib import Path
18
  import time
19
  import gc
20
  import torch
 
 
21
 
22
  from .director import AducDirector
23
  from .types import GenerationState, PreProductionParams, ProductionParams
24
- # --- IMPORTAÇÃO ATUALIZADA ---
25
- # Adicionamos o nosso novo deformes3d_thinker_singleton
26
- from .engineers import deformes2d_thinker_singleton, deformes3d_thinker_singleton, deformes3d_engine_singleton, Deformes4DEngine
27
- # -----------------------------
28
- from .managers import latent_enhancer_specialist_singleton, seedvr_manager_singleton, mmaudio_manager_singleton, vae_manager_singleton
 
 
 
 
 
 
 
 
29
  from .tools.video_encode_tool import video_encode_tool_singleton
30
 
31
  logger = logging.getLogger(__name__)
@@ -33,21 +42,19 @@ ProgressCallback = Optional[Callable[[float, str], None]]
33
 
34
  class AducOrchestrator:
35
  """
36
- Implementa o Maestro (Γ), a camada de orquestração central do Aduc Framework.
37
- Ele coordena os especialistas, gerencia o estado da produção e aplica a
38
- lógica de costura causal nas etapas de pós-produção.
39
  """
40
  def __init__(self, workspace_dir: str):
41
  self.director = AducDirector(workspace_dir)
42
  self.editor = Deformes4DEngine()
43
  self.editor.initialize(workspace_dir)
44
- self.painter = deformes3d_engine_singleton
45
- self.painter.initialize(workspace_dir)
46
  self.device = 'cuda' if torch.cuda.is_available() else 'cpu'
47
  logger.info("ADUC Maestro (Framework Core) pronto para reger a orquestra de especialistas.")
48
 
49
  def get_current_state(self) -> GenerationState:
50
- """Retorna o estado de geração atual, útil para APIs."""
51
  return self.director.get_full_state()
52
 
53
  def process_image_for_story(self, image_path: str, size: int, filename: str) -> str:
@@ -59,68 +66,91 @@ class AducOrchestrator:
59
  logger.info(f"Imagem de referência processada e salva em: {processed_path}")
60
  return processed_path
61
 
62
- # --- ETAPA 1: PRÉ-PRODUÇÃO (STREAMING) ---
63
- def task_pre_production(self, params: PreProductionParams, progress_callback: ProgressCallback = None) -> Generator[Dict[str, Any], None, None]:
64
  """
65
- Orquestra a pré-produção, gerando o roteiro e os keyframes de forma iterativa.
 
66
  """
67
- logger.info("Maestro: Iniciando tarefa de Pré-Produção.")
68
  self.director.update_parameters("pre_producao", params)
69
-
70
- if progress_callback: progress_callback(0.1, "Gerando roteiro com o Deformes3D Thinker (Llama)...")
71
-
72
- # --- MUDANÇA PRINCIPAL AQUI ---
73
- # A chamada para gerar o storyboard agora é feita pelo novo especialista 3D.
74
- storyboard_list = deformes3d_thinker_singleton.generate_storyboard(
75
- prompt=params.prompt, num_keyframes=params.num_keyframes, ref_image_paths=params.ref_paths
76
- )
77
- # -----------------------------
78
-
79
- self.director.update_pre_production_state(params.prompt, params.ref_paths, storyboard_list)
80
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  yield {
82
- "storyboard": storyboard_list,
83
- "updated_state": self.director.get_full_state_as_dict()
 
 
 
84
  }
85
-
86
- if progress_callback: progress_callback(0.2, "Entregando produção ao Diretor Autônomo...")
87
-
88
- final_keyframes_data = []
89
- for keyframes_update in self.painter.generate_keyframes(
90
- generation_state=self.director.get_full_state_as_dict(),
91
- progress_callback=progress_callback
92
- ):
93
- self.director.update_keyframes_state(keyframes_update)
94
- final_keyframes_data = keyframes_update
95
-
96
- yield {
97
- "final_keyframes": [kf["caminho_pixel"] for kf in final_keyframes_data],
98
- "updated_state": self.director.get_full_state_as_dict()
99
- }
100
-
101
- logger.info("Maestro: Tarefa de Pré-Produção concluída.")
102
 
103
- # --- ETAPA 2: PRODUÇÃO ---
104
- def task_produce_original_movie(self, params: ProductionParams, progress_callback: ProgressCallback = None) -> Tuple[str, List[str], GenerationState]:
105
  """Orquestra a geração do vídeo principal a partir dos keyframes via Deformes4DEngine."""
106
  logger.info("Maestro: Iniciando tarefa de Produção do Filme Original.")
107
  self.director.update_parameters("producao", params)
108
-
109
  result_data = self.editor.generate_original_movie(
110
- full_generation_state=self.director.get_full_state_as_dict(),
111
  progress_callback=progress_callback
112
  )
113
  self.director.update_video_state(result_data["video_data"])
114
-
115
  final_video_path = result_data["final_path"]
116
  latent_paths = result_data["latent_paths"]
117
  final_state = self.director.get_full_state()
118
  logger.info("Maestro: Tarefa de Produção do Filme Original concluída.")
119
  return final_video_path, latent_paths, final_state
120
 
121
- # --- ETAPA 3: PÓS-PRODUÇÃO (COM LÓGICA DE COSTURA CAUSAL) ---
122
- def task_run_latent_upscaler(self, latent_paths: List[str], chunk_size: int, progress_callback: ProgressCallback = None) -> Generator[Dict[str, Any], None, None]:
123
- if not latent_paths: raise ValueError("Nenhum caminho de latente fornecido para o upscale.")
124
  logger.info("--- ORQUESTRADOR: Tarefa de Upscaling de Latentes ---")
125
  run_timestamp = int(time.time())
126
  temp_dir = os.path.join(self.director.workspace_dir, f"temp_upscaled_clips_{run_timestamp}")
@@ -129,44 +159,57 @@ class AducOrchestrator:
129
  num_chunks = -(-len(latent_paths) // chunk_size)
130
  for i in range(num_chunks):
131
  chunk_paths = latent_paths[i * chunk_size : (i + 1) * chunk_size]
132
- if progress_callback: progress_callback(i / num_chunks, f"Upscalando & Decodificando Lote {i+1}/{num_chunks}")
 
 
133
  tensors_in_chunk = [torch.load(p, map_location=self.device) for p in chunk_paths]
134
  tensors_to_concat = [t[:, :, :-1, :, :] if j < len(tensors_in_chunk) - 1 else t for j, t in enumerate(tensors_in_chunk)]
135
  sub_group_latent = torch.cat(tensors_to_concat, dim=2)
136
- del tensors_in_chunk, tensors_to_concat; gc.collect(); torch.cuda.empty_cache()
 
 
 
137
  upscaled_latent_chunk = latent_enhancer_specialist_singleton.upscale(sub_group_latent)
138
  pixel_tensor = vae_manager_singleton.decode(upscaled_latent_chunk)
139
  current_clip_path = os.path.join(temp_dir, f"upscaled_clip_{i:04d}.mp4")
140
  self.editor._save_video_from_tensor(pixel_tensor, current_clip_path, fps=24)
141
  final_upscaled_clip_paths.append(current_clip_path)
142
- del sub_group_latent, upscaled_latent_chunk, pixel_tensor; gc.collect(); torch.cuda.empty_cache()
 
 
 
 
143
  yield {"progress": (i + 1) / num_chunks}
 
144
  final_video_path = os.path.join(self.director.workspace_dir, f"upscaled_movie_{run_timestamp}.mp4")
145
  video_encode_tool_singleton.concatenate_videos(final_upscaled_clip_paths, final_video_path, self.director.workspace_dir)
146
  shutil.rmtree(temp_dir)
147
  logger.info(f"Upscaling de latentes completo! Vídeo final em: {final_video_path}")
148
  yield {"final_path": final_video_path}
149
 
150
- def task_run_hd_mastering(self, source_video_path: str, steps: int, prompt: str, progress_callback: ProgressCallback = None) -> Generator[Dict[str, Any], None, None]:
151
  logger.info(f"--- ORQUESTRADOR: Tarefa de Masterização HD com SeedVR ---")
152
  run_timestamp = int(time.time())
153
  output_path = os.path.join(self.director.workspace_dir, f"hd_mastered_movie_{run_timestamp}.mp4")
154
- for update in seedvr_manager_singleton.process_video(
155
- input_video_path=source_video_path, output_video_path=output_path,
156
- prompt=prompt, steps=steps
157
- ):
158
- if progress_callback and "progress" in update:
159
- progress_callback(update["progress"], update.get("desc", "Masterizando HD..."))
160
- if "final_path" in update:
161
- logger.info(f"Masterização HD completa! Vídeo final em: {update['final_path']}")
162
- yield update
163
-
164
- def task_run_audio_generation(self, source_video_path: str, audio_prompt: str, progress_callback: ProgressCallback = None) -> Generator[Dict[str, Any], None, None]:
165
  logger.info(f"--- ORQUESTRADOR: Tarefa de Geração de Áudio ---")
166
- if progress_callback: progress_callback(0.1, "Preparando para geração de áudio...")
 
 
167
  run_timestamp = int(time.time())
168
  source_name = Path(source_video_path).stem
169
  output_path = os.path.join(self.director.workspace_dir, f"{source_name}_with_audio_{run_timestamp}.mp4")
 
170
  try:
171
  result = subprocess.run(
172
  ["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", source_video_path],
@@ -177,11 +220,19 @@ class AducOrchestrator:
177
  logger.error(f"Não foi possível obter a duração do vídeo '{source_video_path}': {e}", exc_info=True)
178
  yield {"error": "Falha ao obter duração do vídeo."}
179
  return
180
- if progress_callback: progress_callback(0.5, "Gerando trilha de áudio...")
 
 
 
181
  final_path = mmaudio_manager_singleton.generate_audio_for_video(
182
- video_path=source_video_path, prompt=audio_prompt,
183
- duration_seconds=duration, output_path_override=output_path
 
 
184
  )
 
185
  logger.info(f"Geração de áudio completa! Vídeo com áudio em: {final_path}")
186
- if progress_callback: progress_callback(1.0, "Geração de áudio completa!")
 
 
187
  yield {"final_path": final_path}
 
2
  #
3
  # Copyright (C) August 4, 2025 Carlos Rodrigues dos Santos
4
  #
5
+ # Versão 7.0.0 (Strategic Maestro with Planner-Composer Delegation)
6
  #
7
+ # O Orquestrador atua como o Maestro (Γ). Ele delega a criação do plano
8
+ # de pré-produção ao Planner2D e a execução ao Composer. Uma vez que o DNA
9
+ # da produção é gerado, ele o utiliza para dirigir as fases de produção
10
+ # (Deformes4DEngine) e pós-produção.
11
 
12
  import logging
13
  from typing import List, Dict, Any, Tuple, Callable, Optional, Generator
14
  from PIL import Image, ImageOps
15
  import os
 
16
  import shutil
 
17
  import time
18
  import gc
19
  import torch
20
+ import subprocess
21
+ from pathlib import Path
22
 
23
  from .director import AducDirector
24
  from .types import GenerationState, PreProductionParams, ProductionParams
25
+
26
+ # Importa a nova hierarquia de planejamento e execução
27
+ from .engineers.planner_2d import planner_2d_singleton
28
+ from .engineers.composer import composer_singleton
29
+
30
+ # Importa os engenheiros e especialistas das fases seguintes
31
+ from .engineers.deformes4D import Deformes4DEngine # Será o Composer4D no futuro
32
+ from .managers import (
33
+ latent_enhancer_specialist_singleton,
34
+ seedvr_manager_singleton,
35
+ mmaudio_manager_singleton,
36
+ vae_manager_singleton
37
+ )
38
  from .tools.video_encode_tool import video_encode_tool_singleton
39
 
40
  logger = logging.getLogger(__name__)
 
42
 
43
  class AducOrchestrator:
44
  """
45
+ Implementa o Maestro (Γ), a camada de orquestração central.
46
+ Coordena os especialistas, gerencia o estado da produção através do Diretor,
47
+ e delega as fases de planejamento e execução.
48
  """
49
  def __init__(self, workspace_dir: str):
50
  self.director = AducDirector(workspace_dir)
51
  self.editor = Deformes4DEngine()
52
  self.editor.initialize(workspace_dir)
 
 
53
  self.device = 'cuda' if torch.cuda.is_available() else 'cpu'
54
  logger.info("ADUC Maestro (Framework Core) pronto para reger a orquestra de especialistas.")
55
 
56
  def get_current_state(self) -> GenerationState:
57
+ """Retorna o estado de geração atual, gerenciado pelo AducDirector."""
58
  return self.director.get_full_state()
59
 
60
  def process_image_for_story(self, image_path: str, size: int, filename: str) -> str:
 
66
  logger.info(f"Imagem de referência processada e salva em: {processed_path}")
67
  return processed_path
68
 
69
+ def task_pre_production(self, params: PreProductionParams, progress_callback: Optional[Callable] = None) -> Generator[Dict[str, Any], None, None]:
 
70
  """
71
+ Orquestra a pré-produção: Planner2D cria o plano, Composer o executa.
72
+ Retransmite as atualizações de progresso para a camada superior.
73
  """
74
+ logger.info("Maestro: Iniciando Fase de Pré-Produção.")
75
  self.director.update_parameters("pre_producao", params)
76
+
77
+ # ETAPA 1.1: PLANEJAMENTO ESTRATÉGICO
78
+ execution_plan = []
79
+ try:
80
+ plan_generator = planner_2d_singleton.generate_execution_plan(
81
+ global_prompt=params.prompt,
82
+ num_scenes=params.num_scenes,
83
+ max_duration_per_act=params.duration_per_fragment,
84
+ callback=progress_callback
85
+ )
86
+ for update in plan_generator:
87
+ yield update # Repassa as atualizações do Planner para a UI
88
+ if update.get("status") == "planning_complete":
89
+ execution_plan = update.get("plan", [])
90
+ except Exception as e:
91
+ logger.error(f"Maestro: Falha crítica na fase de planejamento. Erro: {e}", exc_info=True)
92
+ yield {"status": "error", "message": f"Erro no Planner2D: {e}"}
93
+ return
94
+
95
+ if not execution_plan:
96
+ error_message = "Maestro: Plano de execução retornado pelo Planner2D está vazio."
97
+ logger.error(error_message)
98
+ yield {"status": "error", "message": error_message}
99
+ return
100
+
101
+ # ETAPA 1.2: EXECUÇÃO DO PLANO
102
+ initial_data = {
103
+ "global_prompt": params.prompt,
104
+ "user_media_paths": params.ref_paths,
105
+ }
106
+
107
+ final_dna = {}
108
+ try:
109
+ execution_generator = composer_singleton.execute_plan(execution_plan, initial_data, callback=progress_callback)
110
+ for update in execution_generator:
111
+ yield update # Repassa as atualizações do Composer para a UI
112
+ if update.get('status') == 'complete':
113
+ final_dna = update.get('dna')
114
+ except Exception as e:
115
+ logger.error(f"Maestro: Falha crítica na fase de execução do Composer. Erro: {e}", exc_info=True)
116
+ yield {"status": "error", "message": f"Erro no Composer: {e}"}
117
+ return
118
+
119
+ # ETAPA 1.3: FINALIZAÇÃO E ATUALIZAÇÃO DO ESTADO GLOBAL
120
+ # A lógica para atualizar o AducDirector com o DNA final iria aqui.
121
+ # self.director.update_state_from_dna(final_dna)
122
+ logger.info("Maestro: Pré-Produção concluída. DNA Bruto gerado.")
123
+
124
+ # O Composer3D usaria o 'final_dna' para gerar os keyframes.
125
+ # Por enquanto, retornamos um placeholder para a UI.
126
  yield {
127
+ "status": "pre_production_complete",
128
+ "progress": 1.0,
129
+ "message": "Pré-produção e planejamento concluídos!",
130
+ "dna_snapshot": final_dna,
131
+ "keyframe_gallery": [path for path in params.ref_paths],
132
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
+ def task_produce_original_movie(self, params: ProductionParams, progress_callback: Optional[Callable] = None) -> Tuple[str, List[str], GenerationState]:
 
135
  """Orquestra a geração do vídeo principal a partir dos keyframes via Deformes4DEngine."""
136
  logger.info("Maestro: Iniciando tarefa de Produção do Filme Original.")
137
  self.director.update_parameters("producao", params)
138
+
139
  result_data = self.editor.generate_original_movie(
140
+ full_generation_state=self.director.get_full_state_as_dict(),
141
  progress_callback=progress_callback
142
  )
143
  self.director.update_video_state(result_data["video_data"])
144
+
145
  final_video_path = result_data["final_path"]
146
  latent_paths = result_data["latent_paths"]
147
  final_state = self.director.get_full_state()
148
  logger.info("Maestro: Tarefa de Produção do Filme Original concluída.")
149
  return final_video_path, latent_paths, final_state
150
 
151
+ def task_run_latent_upscaler(self, latent_paths: List[str], chunk_size: int, progress_callback: Optional[Callable] = None) -> Generator[Dict[str, Any], None, None]:
152
+ if not latent_paths:
153
+ raise ValueError("Nenhum caminho de latente fornecido para o upscale.")
154
  logger.info("--- ORQUESTRADOR: Tarefa de Upscaling de Latentes ---")
155
  run_timestamp = int(time.time())
156
  temp_dir = os.path.join(self.director.workspace_dir, f"temp_upscaled_clips_{run_timestamp}")
 
159
  num_chunks = -(-len(latent_paths) // chunk_size)
160
  for i in range(num_chunks):
161
  chunk_paths = latent_paths[i * chunk_size : (i + 1) * chunk_size]
162
+ if progress_callback:
163
+ progress_callback(i / num_chunks, f"Upscalando & Decodificando Lote {i+1}/{num_chunks}")
164
+
165
  tensors_in_chunk = [torch.load(p, map_location=self.device) for p in chunk_paths]
166
  tensors_to_concat = [t[:, :, :-1, :, :] if j < len(tensors_in_chunk) - 1 else t for j, t in enumerate(tensors_in_chunk)]
167
  sub_group_latent = torch.cat(tensors_to_concat, dim=2)
168
+ del tensors_in_chunk, tensors_to_concat
169
+ gc.collect()
170
+ torch.cuda.empty_cache()
171
+
172
  upscaled_latent_chunk = latent_enhancer_specialist_singleton.upscale(sub_group_latent)
173
  pixel_tensor = vae_manager_singleton.decode(upscaled_latent_chunk)
174
  current_clip_path = os.path.join(temp_dir, f"upscaled_clip_{i:04d}.mp4")
175
  self.editor._save_video_from_tensor(pixel_tensor, current_clip_path, fps=24)
176
  final_upscaled_clip_paths.append(current_clip_path)
177
+
178
+ del sub_group_latent, upscaled_latent_chunk, pixel_tensor
179
+ gc.collect()
180
+ torch.cuda.empty_cache()
181
+
182
  yield {"progress": (i + 1) / num_chunks}
183
+
184
  final_video_path = os.path.join(self.director.workspace_dir, f"upscaled_movie_{run_timestamp}.mp4")
185
  video_encode_tool_singleton.concatenate_videos(final_upscaled_clip_paths, final_video_path, self.director.workspace_dir)
186
  shutil.rmtree(temp_dir)
187
  logger.info(f"Upscaling de latentes completo! Vídeo final em: {final_video_path}")
188
  yield {"final_path": final_video_path}
189
 
190
+ def task_run_hd_mastering(self, source_video_path: str, steps: int, prompt: str, progress_callback: Optional[Callable] = None) -> Generator[Dict[str, Any], None, None]:
191
  logger.info(f"--- ORQUESTRADOR: Tarefa de Masterização HD com SeedVR ---")
192
  run_timestamp = int(time.time())
193
  output_path = os.path.join(self.director.workspace_dir, f"hd_mastered_movie_{run_timestamp}.mp4")
194
+
195
+ # O yield from delega a geração de updates para o manager
196
+ yield from seedvr_manager_singleton.process_video(
197
+ input_video_path=source_video_path,
198
+ output_video_path=output_path,
199
+ prompt=prompt,
200
+ steps=steps,
201
+ progress_callback=progress_callback
202
+ )
203
+
204
+ def task_run_audio_generation(self, source_video_path: str, audio_prompt: str, progress_callback: Optional[Callable] = None) -> Generator[Dict[str, Any], None, None]:
205
  logger.info(f"--- ORQUESTRADOR: Tarefa de Geração de Áudio ---")
206
+ if progress_callback:
207
+ progress_callback(0.1, "Preparando para geração de áudio...")
208
+
209
  run_timestamp = int(time.time())
210
  source_name = Path(source_video_path).stem
211
  output_path = os.path.join(self.director.workspace_dir, f"{source_name}_with_audio_{run_timestamp}.mp4")
212
+
213
  try:
214
  result = subprocess.run(
215
  ["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", source_video_path],
 
220
  logger.error(f"Não foi possível obter a duração do vídeo '{source_video_path}': {e}", exc_info=True)
221
  yield {"error": "Falha ao obter duração do vídeo."}
222
  return
223
+
224
+ if progress_callback:
225
+ progress_callback(0.5, "Gerando trilha de áudio...")
226
+
227
  final_path = mmaudio_manager_singleton.generate_audio_for_video(
228
+ video_path=source_video_path,
229
+ prompt=audio_prompt,
230
+ duration_seconds=duration,
231
+ output_path_override=output_path
232
  )
233
+
234
  logger.info(f"Geração de áudio completa! Vídeo com áudio em: {final_path}")
235
+ if progress_callback:
236
+ progress_callback(1.0, "Geração de áudio completa!")
237
+
238
  yield {"final_path": final_path}