Sam commited on
Commit
0f27c29
·
verified ·
1 Parent(s): 9aea45e

Update aduc_framework/orchestrator.py

Browse files
Files changed (1) hide show
  1. aduc_framework/orchestrator.py +103 -55
aduc_framework/orchestrator.py CHANGED
@@ -2,62 +2,65 @@
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__)
41
  ProgressCallback = Optional[Callable[[float, str], None]]
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:
 
 
61
  """Processa e padroniza uma imagem de referência para o formato quadrado."""
62
  img = Image.open(image_path).convert("RGB")
63
  img_square = ImageOps.fit(img, (size, size), Image.Resampling.LANCZOS)
@@ -66,7 +69,9 @@ class AducOrchestrator:
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.
@@ -81,14 +86,17 @@ class AducOrchestrator:
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
 
@@ -106,13 +114,18 @@ class AducOrchestrator:
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
 
@@ -121,8 +134,6 @@ class AducOrchestrator:
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,
@@ -131,14 +142,17 @@ class AducOrchestrator:
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
 
@@ -148,20 +162,30 @@ class AducOrchestrator:
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}")
 
 
157
  os.makedirs(temp_dir, exist_ok=True)
158
  final_upscaled_clip_paths = []
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)
@@ -174,65 +198,89 @@ class AducOrchestrator:
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],
216
- capture_output=True, text=True, check=True
 
 
 
217
  )
218
  duration = float(result.stdout.strip())
219
  except Exception as e:
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}
 
2
  #
3
  # Copyright (C) August 4, 2025 Carlos Rodrigues dos Santos
4
  #
5
+ # Versão 8.0.0 (Strategic Maestro with Full Callback Chaining)
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
+ # e pós-produção, retransmitindo todos os eventos de progresso para a UI.
11
 
12
  import logging
 
 
13
  import os
14
  import shutil
15
  import time
16
  import gc
 
17
  import subprocess
18
  from pathlib import Path
19
+ from typing import Any, Callable, Dict, Generator, List, Optional, Tuple
20
 
21
+ import torch
22
+ from PIL import Image, ImageOps
23
 
24
+ from .director import AducDirector
 
25
  from .engineers.composer import composer_singleton
26
+ from .engineers.deformes4D import Deformes4DEngine
27
+ from .engineers.planner_2d import planner_2d_singleton
 
28
  from .managers import (
29
  latent_enhancer_specialist_singleton,
 
30
  mmaudio_manager_singleton,
31
+ seedvr_manager_singleton,
32
+ vae_manager_singleton,
33
  )
34
  from .tools.video_encode_tool import video_encode_tool_singleton
35
+ from .types import GenerationState, PreProductionParams, ProductionParams
36
 
37
  logger = logging.getLogger(__name__)
38
  ProgressCallback = Optional[Callable[[float, str], None]]
39
 
40
+
41
  class AducOrchestrator:
42
  """
43
  Implementa o Maestro (Γ), a camada de orquestração central.
44
  Coordena os especialistas, gerencia o estado da produção através do Diretor,
45
  e delega as fases de planejamento e execução.
46
  """
47
+
48
  def __init__(self, workspace_dir: str):
49
  self.director = AducDirector(workspace_dir)
50
  self.editor = Deformes4DEngine()
51
  self.editor.initialize(workspace_dir)
52
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
53
+ logger.info(
54
+ "ADUC Maestro (Framework Core) pronto para reger a orquestra de especialistas."
55
+ )
56
 
57
  def get_current_state(self) -> GenerationState:
58
  """Retorna o estado de geração atual, gerenciado pelo AducDirector."""
59
  return self.director.get_full_state()
60
 
61
+ def process_image_for_story(
62
+ self, image_path: str, size: int, filename: str
63
+ ) -> str:
64
  """Processa e padroniza uma imagem de referência para o formato quadrado."""
65
  img = Image.open(image_path).convert("RGB")
66
  img_square = ImageOps.fit(img, (size, size), Image.Resampling.LANCZOS)
 
69
  logger.info(f"Imagem de referência processada e salva em: {processed_path}")
70
  return processed_path
71
 
72
+ def task_pre_production(
73
+ self, params: PreProductionParams, progress_callback: Optional[Callable] = None
74
+ ) -> Generator[Dict[str, Any], None, None]:
75
  """
76
  Orquestra a pré-produção: Planner2D cria o plano, Composer o executa.
77
  Retransmite as atualizações de progresso para a camada superior.
 
86
  global_prompt=params.prompt,
87
  num_scenes=params.num_scenes,
88
  max_duration_per_act=params.duration_per_fragment,
89
+ callback=progress_callback,
90
  )
91
  for update in plan_generator:
92
  yield update # Repassa as atualizações do Planner para a UI
93
  if update.get("status") == "planning_complete":
94
  execution_plan = update.get("plan", [])
95
  except Exception as e:
96
+ logger.error(
97
+ f"Maestro: Falha crítica na fase de planejamento. Erro: {e}",
98
+ exc_info=True,
99
+ )
100
  yield {"status": "error", "message": f"Erro no Planner2D: {e}"}
101
  return
102
 
 
114
 
115
  final_dna = {}
116
  try:
117
+ execution_generator = composer_singleton.execute_plan(
118
+ execution_plan, initial_data, callback=progress_callback
119
+ )
120
  for update in execution_generator:
121
  yield update # Repassa as atualizações do Composer para a UI
122
+ if update.get("status") == "complete":
123
+ final_dna = update.get("dna")
124
  except Exception as e:
125
+ logger.error(
126
+ f"Maestro: Falha crítica na fase de execução do Composer. Erro: {e}",
127
+ exc_info=True,
128
+ )
129
  yield {"status": "error", "message": f"Erro no Composer: {e}"}
130
  return
131
 
 
134
  # self.director.update_state_from_dna(final_dna)
135
  logger.info("Maestro: Pré-Produção concluída. DNA Bruto gerado.")
136
 
 
 
137
  yield {
138
  "status": "pre_production_complete",
139
  "progress": 1.0,
 
142
  "keyframe_gallery": [path for path in params.ref_paths],
143
  }
144
 
145
+ def task_produce_original_movie(
146
+ self, params: ProductionParams, progress_callback: Optional[Callable] = None
147
+ ) -> Tuple[str, List[str], GenerationState]:
148
+ """Orquestra a geração do vídeo principal a partir do DNA e keyframes."""
149
  logger.info("Maestro: Iniciando tarefa de Produção do Filme Original.")
150
  self.director.update_parameters("producao", params)
151
 
152
+ # A implementação futura do Composer3D e 4D lerá o DNA do self.director
153
  result_data = self.editor.generate_original_movie(
154
  full_generation_state=self.director.get_full_state_as_dict(),
155
+ progress_callback=progress_callback,
156
  )
157
  self.director.update_video_state(result_data["video_data"])
158
 
 
162
  logger.info("Maestro: Tarefa de Produção do Filme Original concluída.")
163
  return final_video_path, latent_paths, final_state
164
 
165
+ def task_run_latent_upscaler(
166
+ self,
167
+ latent_paths: List[str],
168
+ chunk_size: int,
169
+ progress_callback: Optional[Callable] = None,
170
+ ) -> Generator[Dict[str, Any], None, None]:
171
  if not latent_paths:
172
  raise ValueError("Nenhum caminho de latente fornecido para o upscale.")
173
  logger.info("--- ORQUESTRADOR: Tarefa de Upscaling de Latentes ---")
174
  run_timestamp = int(time.time())
175
+ temp_dir = os.path.join(
176
+ self.director.workspace_dir, f"temp_upscaled_clips_{run_timestamp}"
177
+ )
178
  os.makedirs(temp_dir, exist_ok=True)
179
  final_upscaled_clip_paths = []
180
  num_chunks = -(-len(latent_paths) // chunk_size)
181
+
182
  for i in range(num_chunks):
183
  chunk_paths = latent_paths[i * chunk_size : (i + 1) * chunk_size]
184
  if progress_callback:
185
+ progress_callback(
186
+ i / num_chunks, f"Upscalando & Decodificando Lote {i+1}/{num_chunks}"
187
+ )
188
+
189
  tensors_in_chunk = [torch.load(p, map_location=self.device) for p in chunk_paths]
190
  tensors_to_concat = [t[:, :, :-1, :, :] if j < len(tensors_in_chunk) - 1 else t for j, t in enumerate(tensors_in_chunk)]
191
  sub_group_latent = torch.cat(tensors_to_concat, dim=2)
 
198
  current_clip_path = os.path.join(temp_dir, f"upscaled_clip_{i:04d}.mp4")
199
  self.editor._save_video_from_tensor(pixel_tensor, current_clip_path, fps=24)
200
  final_upscaled_clip_paths.append(current_clip_path)
201
+
202
  del sub_group_latent, upscaled_latent_chunk, pixel_tensor
203
  gc.collect()
204
  torch.cuda.empty_cache()
205
+
206
  yield {"progress": (i + 1) / num_chunks}
207
+
208
+ final_video_path = os.path.join(
209
+ self.director.workspace_dir, f"upscaled_movie_{run_timestamp}.mp4"
210
+ )
211
+ video_encode_tool_singleton.concatenate_videos(
212
+ final_upscaled_clip_paths, final_video_path, self.director.workspace_dir
213
+ )
214
  shutil.rmtree(temp_dir)
215
  logger.info(f"Upscaling de latentes completo! Vídeo final em: {final_video_path}")
216
  yield {"final_path": final_video_path}
217
 
218
+ def task_run_hd_mastering(
219
+ self,
220
+ source_video_path: str,
221
+ steps: int,
222
+ prompt: str,
223
+ progress_callback: Optional[Callable] = None,
224
+ ) -> Generator[Dict[str, Any], None, None]:
225
  logger.info(f"--- ORQUESTRADOR: Tarefa de Masterização HD com SeedVR ---")
226
  run_timestamp = int(time.time())
227
+ output_path = os.path.join(
228
+ self.director.workspace_dir, f"hd_mastered_movie_{run_timestamp}.mp4"
229
+ )
230
+
231
  yield from seedvr_manager_singleton.process_video(
232
  input_video_path=source_video_path,
233
  output_video_path=output_path,
234
  prompt=prompt,
235
  steps=steps,
236
+ progress_callback=progress_callback,
237
  )
238
+
239
+ def task_run_audio_generation(
240
+ self,
241
+ source_video_path: str,
242
+ audio_prompt: str,
243
+ progress_callback: Optional[Callable] = None,
244
+ ) -> Generator[Dict[str, Any], None, None]:
245
  logger.info(f"--- ORQUESTRADOR: Tarefa de Geração de Áudio ---")
246
  if progress_callback:
247
  progress_callback(0.1, "Preparando para geração de áudio...")
248
+
249
  run_timestamp = int(time.time())
250
  source_name = Path(source_video_path).stem
251
+ output_path = os.path.join(
252
+ self.director.workspace_dir, f"{source_name}_with_audio_{run_timestamp}.mp4"
253
+ )
254
+
255
  try:
256
  result = subprocess.run(
257
+ [
258
+ "ffprobe", "-v", "error", "-show_entries", "format=duration",
259
+ "-of", "default=noprint_wrappers=1:nokey=1", source_video_path,
260
+ ],
261
+ capture_output=True, text=True, check=True,
262
  )
263
  duration = float(result.stdout.strip())
264
  except Exception as e:
265
+ logger.error(
266
+ f"Não foi possível obter a duração do vídeo '{source_video_path}': {e}",
267
+ exc_info=True,
268
+ )
269
  yield {"error": "Falha ao obter duração do vídeo."}
270
  return
271
 
272
  if progress_callback:
273
  progress_callback(0.5, "Gerando trilha de áudio...")
274
+
275
  final_path = mmaudio_manager_singleton.generate_audio_for_video(
276
  video_path=source_video_path,
277
  prompt=audio_prompt,
278
  duration_seconds=duration,
279
+ output_path_override=output_path,
280
  )
281
+
282
  logger.info(f"Geração de áudio completa! Vídeo com áudio em: {final_path}")
283
  if progress_callback:
284
  progress_callback(1.0, "Geração de áudio completa!")
285
+
286
  yield {"final_path": final_path}