euiia commited on
Commit
ce26c61
·
verified ·
1 Parent(s): 7f016c1

Update deformes4D_engine.py

Browse files
Files changed (1) hide show
  1. deformes4D_engine.py +63 -32
deformes4D_engine.py CHANGED
@@ -29,7 +29,11 @@ logger = logging.getLogger(__name__)
29
 
30
  @dataclass
31
  class LatentConditioningItem:
32
- """Representa uma âncora de condicionamento no espaço latente para a Câmera (Ψ)."""
 
 
 
 
33
  latent_tensor: torch.Tensor
34
  media_frame_number: int
35
  conditioning_strength: float
@@ -37,8 +41,9 @@ class LatentConditioningItem:
37
  class Deformes4DEngine:
38
  """
39
  Implementa a Câmera (Ψ) e o Destilador (Δ) da arquitetura ADUC-SDR.
40
- É responsável pela execução da geração de fragmentos de vídeo e pela
41
- extração dos contextos causais (Eco e Déjà-Vu).
 
42
  """
43
  def __init__(self, ltx_manager, workspace_dir="deformes_workspace"):
44
  self.ltx_manager = ltx_manager
@@ -49,6 +54,7 @@ class Deformes4DEngine:
49
 
50
  @property
51
  def vae(self):
 
52
  if self._vae is None:
53
  self._vae = self.ltx_manager.workers[0].pipeline.vae
54
  self._vae.to(self.device); self._vae.eval()
@@ -57,23 +63,28 @@ class Deformes4DEngine:
57
  # MÉTODOS AUXILIARES DE MANIPULAÇÃO DE DADOS E VÍDEO
58
 
59
  def save_latent_tensor(self, tensor: torch.Tensor, path: str):
 
60
  torch.save(tensor.cpu(), path)
61
 
62
  def load_latent_tensor(self, path: str) -> torch.Tensor:
 
63
  return torch.load(path, map_location=self.device)
64
 
65
  @torch.no_grad()
66
  def pixels_to_latents(self, tensor: torch.Tensor) -> torch.Tensor:
 
67
  tensor = tensor.to(self.device, dtype=self.vae.dtype)
68
  return vae_encode(tensor, self.vae, vae_per_channel_normalize=True)
69
 
70
  @torch.no_grad()
71
  def latents_to_pixels(self, latent_tensor: torch.Tensor, decode_timestep: float = 0.05) -> torch.Tensor:
 
72
  latent_tensor = latent_tensor.to(self.device, dtype=self.vae.dtype)
73
  timestep_tensor = torch.tensor([decode_timestep] * latent_tensor.shape[0], device=self.device, dtype=latent_tensor.dtype)
74
  return vae_decode(latent_tensor, self.vae, is_video=True, timestep=timestep_tensor, vae_per_channel_normalize=True)
75
 
76
  def save_video_from_tensor(self, video_tensor: torch.Tensor, path: str, fps: int = 24):
 
77
  if video_tensor is None or video_tensor.ndim != 5 or video_tensor.shape[2] == 0:
78
  logger.warning(f"Tentativa de salvar um tensor de vídeo inválido em {path}. Abortando.")
79
  return
@@ -84,11 +95,13 @@ class Deformes4DEngine:
84
  for frame in video_np: writer.append_data(frame)
85
 
86
  def _preprocess_image_for_latent_conversion(self, image: Image.Image, target_resolution: tuple) -> Image.Image:
 
87
  if image.size != target_resolution:
88
  return ImageOps.fit(image, target_resolution, Image.Resampling.LANCZOS)
89
  return image
90
 
91
  def pil_to_latent(self, pil_image: Image.Image) -> torch.Tensor:
 
92
  image_np = np.array(pil_image).astype(np.float32) / 255.0
93
  tensor = torch.from_numpy(image_np).permute(2, 0, 1).unsqueeze(0).unsqueeze(2)
94
  tensor = (tensor * 2.0) - 1.0
@@ -109,7 +122,11 @@ class Deformes4DEngine:
109
  return None
110
 
111
  def _trim_last_frame_ffmpeg(self, input_path: str, output_path: str) -> bool:
112
- """Cria uma cópia de um vídeo, removendo o último frame."""
 
 
 
 
113
  frame_count = self._get_video_frame_count(input_path)
114
  if frame_count is None or frame_count < 2:
115
  logger.warning(f"Não foi possível podar o último frame de {input_path}. O vídeo é muito curto ou ocorreu um erro.")
@@ -130,7 +147,8 @@ class Deformes4DEngine:
130
 
131
  def _generate_video_from_latents(self, latent_tensor, base_name: str) -> str:
132
  """
133
- Gera um vídeo a partir de latentes, podando o último frame para garantir concatenação limpa.
 
134
  """
135
  untrimmed_video_path = os.path.join(self.workspace_dir, f"{base_name}_untrimmed.mp4")
136
  trimmed_video_path = os.path.join(self.workspace_dir, f"{base_name}.mp4")
@@ -155,7 +173,7 @@ class Deformes4DEngine:
155
  return trimmed_video_path
156
 
157
  def concatenate_videos_ffmpeg(self, video_paths: list[str], output_path: str) -> str:
158
- """Concatena uma lista de arquivos de vídeo em um único arquivo usando FFmpeg."""
159
  if not video_paths:
160
  raise gr.Error("Nenhum fragmento de vídeo para montar.")
161
 
@@ -181,11 +199,23 @@ class Deformes4DEngine:
181
  handler_strength: float, destination_convergence_strength: float,
182
  video_resolution: int, use_continuity_director: bool,
183
  progress: gr.Progress = gr.Progress()):
 
 
 
 
 
 
 
 
 
 
 
 
184
 
185
  # 1. Definição dos Parâmetros da Geração com base na Tese
186
  FPS = 24
187
- FRAMES_PER_LATENT_CHUNK = 8
188
- ECO_LATENT_CHUNKS = 2
189
 
190
  total_frames_brutos = self._quantize_to_multiple(int(seconds_per_fragment * FPS), FRAMES_PER_LATENT_CHUNK)
191
  total_latents_brutos = total_frames_brutos // FRAMES_PER_LATENT_CHUNK
@@ -244,11 +274,11 @@ class Deformes4DEngine:
244
  conditioning_items = []
245
  logger.info(" [Ψ.1] Montando âncoras causais...")
246
 
247
- if eco_latent_for_next_loop is None:
248
- logger.info(" - Primeiro fragmento: Usando Keyframe inicial como âncora de partida.")
249
  img_start = self._preprocess_image_for_latent_conversion(Image.open(start_keyframe_path).convert("RGB"), target_resolution_tuple)
250
  conditioning_items.append(LatentConditioningItem(self.pil_to_latent(img_start), 0, 1.0))
251
- else:
252
  logger.info(" - Âncora 1: Eco Causal (C) - Herança do passado.")
253
  conditioning_items.append(LatentConditioningItem(eco_latent_for_next_loop, 0, 1.0))
254
  logger.info(" - Âncora 2: Déjà-Vu (D) - Memória de um futuro idealizado.")
@@ -264,31 +294,31 @@ class Deformes4DEngine:
264
  latents_brutos = self._generate_latent_tensor_internal(conditioning_items, current_ltx_params, target_resolution_tuple, total_frames_brutos)
265
  logger.info(f" - Geração concluída. Tensor latente bruto (V_bruto) criado com shape: {latents_brutos.shape}.")
266
 
267
- # 3.4. Execução do Destilador (Δ): Implementação do Ciclo de Poda Causal (com workaround empírico)
 
 
268
  logger.info(f" [Δ] Destilador (Δ) executando o Ciclo de Poda Causal...")
269
-
270
-
271
- last_trim = latents_brutos[:, :, -(latents_a_podar+1):, :, :].clone()
272
- eco_latent_for_next_loop = last_trim[:, :, :2, :, :].clone()
273
- dejavu_latent_for_next_loop = last_trim[:, :, -1:, :, :].clone()
274
 
275
- latents_video = latents_brutos[:, :, :-(latents_a_podar-1), :, :].clone()
276
- latents_video = latents_video[:, :, 1:, :, :]
277
-
278
- logger.info(f" [Δ] latents_video {latents_video.shape}")
279
-
280
 
281
- #last_trim = latents_brutos[:, :, -(latents_a_podar + 2):, :, :].clone()
282
- #eco_latent_for_next_loop = last_trim[:, :, :ECO_LATENT_CHUNKS, :, :].clone()
283
- #dejavu_latent_for_next_loop = last_trim[:, :, -1:, :, :].clone()
284
 
285
- #latents_video = latents_brutos[:, :, :-(latents_a_podar + 2), :, :].clone()
286
- #latents_video = latents_video[:, :, 2:, :, :]
 
 
 
 
 
 
287
 
288
- #logger.info(f" [Δ] Shape do tensor para vídeo final: {latents_video.shape}")
289
  logger.info(f" - (Δ.1) Déjà-Vu (D) destilado. Shape: {dejavu_latent_for_next_loop.shape}")
290
  logger.info(f" - (Δ.2) Eco Causal (C) extraído. Shape: {eco_latent_for_next_loop.shape}")
291
 
 
292
  if transition_type == "cut":
293
  logger.warning(" - DECISÃO DO MAESTRO: Corte ('cut'). Resetando a memória causal (Eco e Déjà-Vu).")
294
  eco_latent_for_next_loop = None
@@ -300,15 +330,14 @@ class Deformes4DEngine:
300
  video_clips_paths.append(video_path)
301
  logger.info(f"--- FRAGMENTO {fragment_index} FINALIZADO E SALVO EM: {video_path} ---")
302
 
303
- # Bloco de Diagnóstico: Gera um vídeo a partir do tensor do Eco
304
  if eco_latent_for_next_loop is not None:
305
  logger.info("--- GERANDO VÍDEO DE DIAGNÓSTICO DO ECO CAUSAL ---")
306
  eco_base_name = f"fragment_{fragment_index}_eco_diagnostic_{int(time.time())}"
307
  eco_video_path = self._generate_video_from_latents(eco_latent_for_next_loop, eco_base_name)
308
- #video_clips_paths.append(eco_video_path)
309
  logger.info(f"Vídeo de diagnóstico do Eco salvo em: {eco_video_path} e adicionado à concatenação.")
310
- yield {"fragment_path": eco_video_path}
311
-
312
  yield {"fragment_path": video_path}
313
 
314
  # 4. Montagem Final do Filme
@@ -319,6 +348,7 @@ class Deformes4DEngine:
319
  yield {"final_path": final_movie_path}
320
 
321
  def _generate_latent_tensor_internal(self, conditioning_items, ltx_params, target_resolution, total_frames_to_generate):
 
322
  final_ltx_params = {
323
  **ltx_params, 'width': target_resolution[0], 'height': target_resolution[1],
324
  'video_total_frames': total_frames_to_generate, 'video_fps': 24,
@@ -330,6 +360,7 @@ class Deformes4DEngine:
330
  return new_full_latents
331
 
332
  def _quantize_to_multiple(self, n, m):
 
333
  if m == 0: return n
334
  quantized = int(round(n / m) * m)
335
  return m if n > 0 and quantized == 0 else quantized
 
29
 
30
  @dataclass
31
  class LatentConditioningItem:
32
+ """
33
+ Representa uma âncora de condicionamento no espaço latente para a Câmera (Ψ).
34
+ Cada item define um tensor, o frame exato onde sua influência é máxima,
35
+ e a força dessa influência.
36
+ """
37
  latent_tensor: torch.Tensor
38
  media_frame_number: int
39
  conditioning_strength: float
 
41
  class Deformes4DEngine:
42
  """
43
  Implementa a Câmera (Ψ) e o Destilador (Δ) da arquitetura ADUC-SDR.
44
+ Esta classe é o coração da execução, responsável pela geração de fragmentos de vídeo
45
+ e pela extração e aplicação dos contextos causais (Eco e Déjà-Vu) que garantem
46
+ a coerência de longa duração.
47
  """
48
  def __init__(self, ltx_manager, workspace_dir="deformes_workspace"):
49
  self.ltx_manager = ltx_manager
 
54
 
55
  @property
56
  def vae(self):
57
+ """Acessa o decodificador VAE de forma lazy, garantindo que ele esteja na GPU."""
58
  if self._vae is None:
59
  self._vae = self.ltx_manager.workers[0].pipeline.vae
60
  self._vae.to(self.device); self._vae.eval()
 
63
  # MÉTODOS AUXILIARES DE MANIPULAÇÃO DE DADOS E VÍDEO
64
 
65
  def save_latent_tensor(self, tensor: torch.Tensor, path: str):
66
+ """Salva um tensor PyTorch no disco."""
67
  torch.save(tensor.cpu(), path)
68
 
69
  def load_latent_tensor(self, path: str) -> torch.Tensor:
70
+ """Carrega um tensor PyTorch do disco para o dispositivo correto."""
71
  return torch.load(path, map_location=self.device)
72
 
73
  @torch.no_grad()
74
  def pixels_to_latents(self, tensor: torch.Tensor) -> torch.Tensor:
75
+ """Converte um tensor de pixels (vídeo) para o espaço latente usando o VAE."""
76
  tensor = tensor.to(self.device, dtype=self.vae.dtype)
77
  return vae_encode(tensor, self.vae, vae_per_channel_normalize=True)
78
 
79
  @torch.no_grad()
80
  def latents_to_pixels(self, latent_tensor: torch.Tensor, decode_timestep: float = 0.05) -> torch.Tensor:
81
+ """Converte um tensor latente de volta para um tensor de pixels (vídeo) usando o VAE."""
82
  latent_tensor = latent_tensor.to(self.device, dtype=self.vae.dtype)
83
  timestep_tensor = torch.tensor([decode_timestep] * latent_tensor.shape[0], device=self.device, dtype=latent_tensor.dtype)
84
  return vae_decode(latent_tensor, self.vae, is_video=True, timestep=timestep_tensor, vae_per_channel_normalize=True)
85
 
86
  def save_video_from_tensor(self, video_tensor: torch.Tensor, path: str, fps: int = 24):
87
+ """Salva um tensor de pixels como um arquivo de vídeo MP4."""
88
  if video_tensor is None or video_tensor.ndim != 5 or video_tensor.shape[2] == 0:
89
  logger.warning(f"Tentativa de salvar um tensor de vídeo inválido em {path}. Abortando.")
90
  return
 
95
  for frame in video_np: writer.append_data(frame)
96
 
97
  def _preprocess_image_for_latent_conversion(self, image: Image.Image, target_resolution: tuple) -> Image.Image:
98
+ """Redimensiona uma imagem para a resolução alvo antes de convertê-la para latente."""
99
  if image.size != target_resolution:
100
  return ImageOps.fit(image, target_resolution, Image.Resampling.LANCZOS)
101
  return image
102
 
103
  def pil_to_latent(self, pil_image: Image.Image) -> torch.Tensor:
104
+ """Converte uma imagem PIL para um tensor latente."""
105
  image_np = np.array(pil_image).astype(np.float32) / 255.0
106
  tensor = torch.from_numpy(image_np).permute(2, 0, 1).unsqueeze(0).unsqueeze(2)
107
  tensor = (tensor * 2.0) - 1.0
 
122
  return None
123
 
124
  def _trim_last_frame_ffmpeg(self, input_path: str, output_path: str) -> bool:
125
+ """
126
+ Cria uma cópia de um vídeo, removendo o último frame.
127
+ Esta etapa é CRUCIAL para resolver o problema do "frame n+1", onde o VAE
128
+ gera um frame extra, o que causaria "soluços" na concatenação.
129
+ """
130
  frame_count = self._get_video_frame_count(input_path)
131
  if frame_count is None or frame_count < 2:
132
  logger.warning(f"Não foi possível podar o último frame de {input_path}. O vídeo é muito curto ou ocorreu um erro.")
 
147
 
148
  def _generate_video_from_latents(self, latent_tensor, base_name: str) -> str:
149
  """
150
+ Gera um vídeo a partir de latentes e aplica a poda do último frame.
151
+ Este processo de duas etapas garante que os fragmentos para concatenação sejam perfeitamente limpos.
152
  """
153
  untrimmed_video_path = os.path.join(self.workspace_dir, f"{base_name}_untrimmed.mp4")
154
  trimmed_video_path = os.path.join(self.workspace_dir, f"{base_name}.mp4")
 
173
  return trimmed_video_path
174
 
175
  def concatenate_videos_ffmpeg(self, video_paths: list[str], output_path: str) -> str:
176
+ """Concatena uma lista de arquivos de vídeo em um único arquivo usando FFmpeg com o método 'concat'."""
177
  if not video_paths:
178
  raise gr.Error("Nenhum fragmento de vídeo para montar.")
179
 
 
199
  handler_strength: float, destination_convergence_strength: float,
200
  video_resolution: int, use_continuity_director: bool,
201
  progress: gr.Progress = gr.Progress()):
202
+ """
203
+ Orquestra a geração de um filme completo, fragmento por fragmento, seguindo os princípios da ADUC-SDR.
204
+
205
+ O processo para cada fragmento é:
206
+ 1. Consulta ao Maestro (Γ) para obter a intenção narrativa (motion_prompt).
207
+ 2. Montagem das âncoras causais {C, D, K} para a Câmera (Ψ).
208
+ 3. Execução da Geração Exploratória para criar o tensor bruto (V_bruto).
209
+ 4. Execução do Ciclo de Poda Causal pelo Destilador (Δ) para extrair o Eco (C) e o Déjà-Vu (D) para o próximo ciclo
210
+ e para definir o tensor de vídeo canônico (V_final).
211
+ 5. Renderização do fragmento final e, opcionalmente, de um clipe de diagnóstico do Eco.
212
+ 6. Repetição até que todos os keyframes sejam processados, seguida da montagem final.
213
+ """
214
 
215
  # 1. Definição dos Parâmetros da Geração com base na Tese
216
  FPS = 24
217
+ FRAMES_PER_LATENT_CHUNK = 8 # Fator de conversão: 1 índice na dimensão de tempo do tensor latente = 8 frames de vídeo.
218
+ ECO_LATENT_CHUNKS = 2 # Número de chunks latentes que compõem o Eco Causal (C) para carregar a inércia.
219
 
220
  total_frames_brutos = self._quantize_to_multiple(int(seconds_per_fragment * FPS), FRAMES_PER_LATENT_CHUNK)
221
  total_latents_brutos = total_frames_brutos // FRAMES_PER_LATENT_CHUNK
 
274
  conditioning_items = []
275
  logger.info(" [Ψ.1] Montando âncoras causais...")
276
 
277
+ if eco_latent_for_next_loop is None: # Lógica para o primeiro fragmento ou um corte ("cut")
278
+ logger.info(" - Primeiro fragmento ou corte: Usando Keyframe inicial como âncora de partida.")
279
  img_start = self._preprocess_image_for_latent_conversion(Image.open(start_keyframe_path).convert("RGB"), target_resolution_tuple)
280
  conditioning_items.append(LatentConditioningItem(self.pil_to_latent(img_start), 0, 1.0))
281
+ else: # Lógica para fragmentos contínuos
282
  logger.info(" - Âncora 1: Eco Causal (C) - Herança do passado.")
283
  conditioning_items.append(LatentConditioningItem(eco_latent_for_next_loop, 0, 1.0))
284
  logger.info(" - Âncora 2: Déjà-Vu (D) - Memória de um futuro idealizado.")
 
294
  latents_brutos = self._generate_latent_tensor_internal(conditioning_items, current_ltx_params, target_resolution_tuple, total_frames_brutos)
295
  logger.info(f" - Geração concluída. Tensor latente bruto (V_bruto) criado com shape: {latents_brutos.shape}.")
296
 
297
+ # 3.4. Execução do Destilador (Δ): Implementação do Ciclo de Poda Causal com workaround empírico.
298
+ # Esta lógica foi refinada para contornar um bug do motor de difusão que gera os 2 primeiros chunks com
299
+ # artefatos, garantindo um resultado final limpo e mantendo a transferência causal.
300
  logger.info(f" [Δ] Destilador (Δ) executando o Ciclo de Poda Causal...")
 
 
 
 
 
301
 
302
+ # Pega uma fatia da cauda de V_bruto que é grande o suficiente para conter o Eco e o Déjà-Vu.
303
+ last_trim = latents_brutos[:, :, -(latents_a_podar + 2):, :, :].clone()
 
 
 
304
 
305
+ # Extrai o Eco (C) dos 2 chunks iniciais desta fatia.
306
+ eco_latent_for_next_loop = last_trim[:, :, :ECO_LATENT_CHUNKS, :, :].clone()
 
307
 
308
+ # Extrai o Déjà-Vu (D) do último chunk absoluto desta fatia (que é também o último de V_bruto).
309
+ dejavu_latent_for_next_loop = last_trim[:, :, -1:, :, :].clone()
310
+
311
+ # Define o tensor para o vídeo final (V_final) removendo a cauda inteira, incluindo os chunks usados pelo Eco.
312
+ latents_video = latents_brutos[:, :, :-(latents_a_podar + 2), :, :].clone()
313
+
314
+ # Remove cirurgicamente os 2 primeiros chunks instáveis do vídeo final.
315
+ latents_video = latents_video[:, :, 2:, :, :]
316
 
317
+ logger.info(f" [Δ] Shape do tensor para vídeo final: {latents_video.shape}")
318
  logger.info(f" - (Δ.1) Déjà-Vu (D) destilado. Shape: {dejavu_latent_for_next_loop.shape}")
319
  logger.info(f" - (Δ.2) Eco Causal (C) extraído. Shape: {eco_latent_for_next_loop.shape}")
320
 
321
+ # Se o Maestro decidiu por um "corte", a memória causal é resetada para o próximo ciclo.
322
  if transition_type == "cut":
323
  logger.warning(" - DECISÃO DO MAESTRO: Corte ('cut'). Resetando a memória causal (Eco e Déjà-Vu).")
324
  eco_latent_for_next_loop = None
 
330
  video_clips_paths.append(video_path)
331
  logger.info(f"--- FRAGMENTO {fragment_index} FINALIZADO E SALVO EM: {video_path} ---")
332
 
333
+ # Bloco de Diagnóstico: Gera um vídeo a partir do tensor do Eco para validação visual.
334
  if eco_latent_for_next_loop is not None:
335
  logger.info("--- GERANDO VÍDEO DE DIAGNÓSTICO DO ECO CAUSAL ---")
336
  eco_base_name = f"fragment_{fragment_index}_eco_diagnostic_{int(time.time())}"
337
  eco_video_path = self._generate_video_from_latents(eco_latent_for_next_loop, eco_base_name)
338
+ video_clips_paths.append(eco_video_path)
339
  logger.info(f"Vídeo de diagnóstico do Eco salvo em: {eco_video_path} e adicionado à concatenação.")
340
+
 
341
  yield {"fragment_path": video_path}
342
 
343
  # 4. Montagem Final do Filme
 
348
  yield {"final_path": final_movie_path}
349
 
350
  def _generate_latent_tensor_internal(self, conditioning_items, ltx_params, target_resolution, total_frames_to_generate):
351
+ """Função de baixo nível que invoca o motor de difusão para a geração do tensor latente."""
352
  final_ltx_params = {
353
  **ltx_params, 'width': target_resolution[0], 'height': target_resolution[1],
354
  'video_total_frames': total_frames_to_generate, 'video_fps': 24,
 
360
  return new_full_latents
361
 
362
  def _quantize_to_multiple(self, n, m):
363
+ """Garante que um número 'n' seja um múltiplo de 'm', necessário para o fatiamento de tensores."""
364
  if m == 0: return n
365
  quantized = int(round(n / m) * m)
366
  return m if n > 0 and quantized == 0 else quantized