euiia commited on
Commit
887690a
·
verified ·
1 Parent(s): 3b91b34

Update deformes4D_engine.py

Browse files
Files changed (1) hide show
  1. deformes4D_engine.py +81 -135
deformes4D_engine.py CHANGED
@@ -26,7 +26,7 @@ from audio_specialist import audio_specialist_singleton
26
  from ltx_manager_helpers import ltx_manager_singleton
27
  from gemini_helpers import gemini_singleton
28
  from upscaler_specialist import upscaler_specialist_singleton
29
- from hd_specialist import hd_specialist_singleton # Importa o novo especialista
30
  from ltx_video.models.autoencoders.causal_video_autoencoder import CausalVideoAutoencoder
31
  from ltx_video.models.autoencoders.vae_encode import vae_encode, vae_decode
32
 
@@ -58,17 +58,28 @@ class Deformes4DEngine:
58
  self._vae.to(self.device); self._vae.eval()
59
  return self._vae
60
 
61
- # MÉTODOS AUXILIARES
62
- def save_latent_tensor(self, tensor: torch.Tensor, path: str):
63
- torch.save(tensor.cpu(), path)
64
 
65
- def load_latent_tensor(self, path: str) -> torch.Tensor:
66
- return torch.load(path, map_location=self.device)
 
 
 
 
 
 
 
 
 
67
 
68
- @torch.no_grad()
69
- def pixels_to_latents(self, tensor: torch.Tensor) -> torch.Tensor:
70
- tensor = tensor.to(self.device, dtype=self.vae.dtype)
71
- return vae_encode(tensor, self.vae, vae_per_channel_normalize=True)
 
 
 
 
72
 
73
  @torch.no_grad()
74
  def latents_to_pixels(self, latent_tensor: torch.Tensor, decode_timestep: float = 0.05) -> torch.Tensor:
@@ -76,14 +87,6 @@ class Deformes4DEngine:
76
  timestep_tensor = torch.tensor([decode_timestep] * latent_tensor.shape[0], device=self.device, dtype=latent_tensor.dtype)
77
  return vae_decode(latent_tensor, self.vae, is_video=True, timestep=timestep_tensor, vae_per_channel_normalize=True)
78
 
79
- def save_video_from_tensor(self, video_tensor: torch.Tensor, path: str, fps: int = 24):
80
- if video_tensor is None or video_tensor.ndim != 5 or video_tensor.shape[2] == 0: return
81
- video_tensor = video_tensor.squeeze(0).permute(1, 2, 3, 0)
82
- video_tensor = (video_tensor.clamp(-1, 1) + 1) / 2.0
83
- video_np = (video_tensor.detach().cpu().float().numpy() * 255).astype(np.uint8)
84
- with imageio.get_writer(path, fps=fps, codec='libx264', quality=8, output_params=['-pix_fmt', 'yuv420p']) as writer:
85
- for frame in video_np: writer.append_data(frame)
86
-
87
  def _preprocess_image_for_latent_conversion(self, image: Image.Image, target_resolution: tuple) -> Image.Image:
88
  if image.size != target_resolution:
89
  return ImageOps.fit(image, target_resolution, Image.Resampling.LANCZOS)
@@ -95,70 +98,32 @@ class Deformes4DEngine:
95
  tensor = (tensor * 2.0) - 1.0
96
  return self.pixels_to_latents(tensor)
97
 
98
- def _get_video_duration(self, video_path: str) -> float:
99
- if not os.path.exists(video_path): return 0.0
100
- try:
101
- result = subprocess.run(
102
- ["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", video_path],
103
- capture_output=True, text=True, check=True)
104
- return float(result.stdout.strip())
105
- except Exception:
106
- return 0.0
107
-
108
- def _combine_video_and_audio_ffmpeg(self, video_path: str, audio_path: str, output_path: str):
109
- """Combina um arquivo de vídeo com um arquivo de áudio usando ffmpeg."""
110
- cmd = [
111
- 'ffmpeg', '-y',
112
- '-i', video_path,
113
- '-i', audio_path,
114
- '-c:v', 'copy', # Copia o stream de vídeo sem re-codificar
115
- '-c:a', 'aac', # Re-codifica o áudio para o formato AAC, padrão para MP4
116
- '-shortest', # Termina a codificação quando o stream mais curto terminar
117
- output_path
118
- ]
119
- try:
120
- subprocess.run(cmd, check=True, capture_output=True, text=True, encoding='utf-8')
121
- logger.info(f"Áudio e vídeo combinados com sucesso em {output_path}")
122
- except subprocess.CalledProcessError as e:
123
- logger.error(f"Falha ao combinar áudio e vídeo. Detalhes: {e.stderr}")
124
- raise gr.Error(f"Falha ao combinar áudio e vídeo: {e.stderr}")
125
-
126
- def _generate_standalone_audio(self, video_for_duration_path: str, audio_prompt: str) -> str:
127
- """Gera um arquivo de áudio e retorna seu caminho."""
128
- duration = self._get_video_duration(video_for_duration_path)
129
- if duration == 0:
130
- raise gr.Error("Não foi possível determinar a duração do vídeo para gerar o áudio.")
131
-
132
- # Esta função agora deve retornar apenas o caminho do arquivo de áudio gerado
133
- # (pode exigir uma pequena modificação no seu audio_specialist)
134
- audio_path = audio_specialist_singleton.generate_audio(
135
- prompt=audio_prompt,
136
- duration_seconds=duration,
137
- output_dir=self.workspace_dir
138
- )
139
- return audio_path
140
 
141
- # NÚCLEO DA LÓGICA ADUC-SDR
142
  def generate_full_movie(self, keyframes: list, global_prompt: str, storyboard: list,
143
  seconds_per_fragment: float, trim_percent: int,
144
  handler_strength: float, destination_convergence_strength: float,
145
  video_resolution: int, use_continuity_director: bool,
146
  progress: gr.Progress = gr.Progress()):
147
 
148
- TOTAL_STEPS = len(keyframes) - 1 + 5 # Fragmentos + 5 etapas de pós-produção
 
149
  current_step = 0
150
-
151
  FPS = 24
152
  FRAMES_PER_LATENT_CHUNK = 8
153
  ECO_LATENT_CHUNKS = 2
154
 
155
  total_frames_brutos = self._quantize_to_multiple(int(seconds_per_fragment * FPS), FRAMES_PER_LATENT_CHUNK)
156
- total_latents_brutos = total_frames_brutos // FRAMES_PER_LATENT_CHUNK
157
  frames_a_podar = self._quantize_to_multiple(int(total_frames_brutos * (trim_percent / 100)), FRAMES_PER_LATENT_CHUNK)
158
  latents_a_podar = frames_a_podar // FRAMES_PER_LATENT_CHUNK
159
 
160
- if total_latents_brutos <= latents_a_podar + 1:
161
- raise gr.Error(f"A combinação de duração e poda é muito agressiva.")
162
 
163
  DEJAVU_FRAME_TARGET = frames_a_podar - 1 if frames_a_podar > 0 else 0
164
  DESTINATION_FRAME_TARGET = total_frames_brutos - 1
@@ -169,16 +134,14 @@ class Deformes4DEngine:
169
 
170
  eco_latent_for_next_loop = None
171
  dejavu_latent_for_next_loop = None
172
-
173
- num_transitions_to_generate = len(keyframe_paths) - 1
174
  processed_latent_fragments = []
175
 
 
176
  for i in range(num_transitions_to_generate):
177
  fragment_index = i + 1
178
  current_step += 1
179
  progress(current_step / TOTAL_STEPS, desc=f"Gerando Fragmento {fragment_index}/{num_transitions_to_generate}")
180
 
181
- # ... (Lógica de decisão do Gemini e configuração de parâmetros - sem alterações)
182
  past_keyframe_path = keyframe_paths[i - 1] if i > 0 else keyframe_paths[i]
183
  start_keyframe_path = keyframe_paths[i]
184
  destination_keyframe_path = keyframe_paths[i + 1]
@@ -189,7 +152,7 @@ class Deformes4DEngine:
189
  transition_type, motion_prompt = decision["transition_type"], decision["motion_prompt"]
190
  story_history += f"\n- Ato {fragment_index}: {motion_prompt}"
191
 
192
- expected_height, expected_width = 768, 1152
193
  downscale_factor = 2 / 3
194
  downscaled_height = self._quantize_to_multiple(int(expected_height * downscale_factor), 8)
195
  downscaled_width = self._quantize_to_multiple(int(expected_width * downscale_factor), 8)
@@ -217,82 +180,76 @@ class Deformes4DEngine:
217
  if transition_type == "cut":
218
  eco_latent_for_next_loop, dejavu_latent_for_next_loop = None, None
219
 
220
- # --- ATO I: PÓS-PRODUÇÃO LATENTE ---
221
  upscaled_latents = self.upscale_latents(latents_video)
222
  refined_latents = self.refine_latents(upscaled_latents, motion_prompt=f"refining scene: {motion_prompt}")
223
  processed_latent_fragments.append(refined_latents)
224
-
225
- # --- FIM DO LOOP DE GERAÇÃO ---
226
-
227
- current_step += 1
228
- progress(current_step / TOTAL_STEPS, desc="Concatenando fragmentos...")
229
- tensors_para_concatenar = [frag.to(self.device) for frag in processed_latent_fragments]
230
- final_concatenated_latents = torch.cat(tensors_para_concatenar, dim=2)
231
-
232
- base_name = f"movie_{int(time.time())}"
233
 
 
 
234
  current_step += 1
235
- progress(current_step / TOTAL_STEPS, desc="Renderizando vídeo base...")
236
  refined_silent_video_path = os.path.join(self.workspace_dir, f"{base_name}_refined_silent.mp4")
237
- final_pixel_tensor = self.latents_to_pixels(final_concatenated_latents)
238
- self.save_video_from_tensor(final_pixel_tensor, refined_silent_video_path, fps=FPS)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
 
240
- # Limpeza de VRAM antes da próxima etapa pesada
241
- del final_pixel_tensor, final_concatenated_latents, processed_latent_fragments, tensors_para_concatenar
242
  gc.collect()
243
  torch.cuda.empty_cache()
244
 
245
-
246
- # --- ATO II: MASTERIZAÇÃO FINAL (APLICAÇÃO DE HD) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
247
  current_step += 1
248
  progress(current_step / TOTAL_STEPS, desc="Aprimoramento final (HD)...")
249
  hq_silent_video_path = os.path.join(self.workspace_dir, f"{base_name}_hq_silent.mp4")
250
  try:
251
- # O Especialista HD processa o vídeo silencioso refinado
252
  hd_specialist_singleton.process_video(
253
  input_video_path=refined_silent_video_path,
254
  output_video_path=hq_silent_video_path,
255
  prompt=global_prompt
256
  )
257
  except Exception as e:
258
- logger.error(f"Falha no processo de aprimoramento HD. Usando o vídeo refinado como fallback. Erro: {e}")
259
- # Se o HD falhar, usamos o vídeo refinado (silencioso) como base para o final
260
  os.rename(refined_silent_video_path, hq_silent_video_path)
261
 
262
  current_step += 1
263
  progress(current_step / TOTAL_STEPS, desc="Finalizando montagem...")
264
  final_video_path = os.path.join(self.workspace_dir, f"{base_name}_FINAL.mp4")
265
-
266
- #if audio_path and os.path.exists(audio_path):
267
- # # Se o áudio foi gerado, combina o vídeo de ALTA QUALIDADE com ele
268
- # self._combine_video_and_audio_ffmpeg(hq_silent_video_path, audio_path, final_video_path)
269
- #else:
270
- # # Se não houver áudio, apenas renomeia o vídeo de alta qualidade
271
- # os.rename(hq_silent_video_path, final_video_path)
272
-
273
- logger.info(f"Processo concluído! Vídeo final salvo em: {hq_silent_video_path}")
274
- yield {"final_path": hq_silent_video_path}
275
-
276
- def refine_latents1(self, latents: torch.Tensor,
277
- fps: int = 24,
278
- denoise_strength: float = 0.35,
279
- refine_steps: int = 12,
280
- motion_prompt: str = "refining video, improving details, cinematic quality") -> torch.Tensor:
281
- """Aplica um passe de refinamento (denoise) em um tensor latente."""
282
- logger.info(f"Refinando tensor latente com shape {latents.shape}.")
283
- _, _, num_frames, latent_h, latent_w = latents.shape
284
- vae_scale_factor = self.vae.config.scaling_factor if hasattr(self.vae.config, 'scaling_factor') else 8
285
- pixel_height, pixel_width = latent_h * vae_scale_factor, latent_w * vae_scale_factor
286
-
287
- refined_latents_tensor, _ = self.ltx_manager.refine_latents(
288
- latents, height=pixel_height, width=pixel_width, video_total_frames=num_frames,
289
- video_fps=fps, motion_prompt=motion_prompt, current_fragment_index=int(time.time()),
290
- denoise_strength=denoise_strength, refine_steps=refine_steps)
291
 
292
- return refined_latents_tensor
293
-
294
-
 
295
 
 
 
296
 
297
  def refine_latents(self, latents: torch.Tensor,
298
  fps: int = 24,
@@ -305,26 +262,17 @@ class Deformes4DEngine:
305
  """
306
  logger.info(f"Refinando tensor latente com shape {latents.shape} para refinamento.")
307
 
308
- # Extrai as dimensões do tensor latente de ENTRADA.
309
  _, _, num_latent_frames, latent_h, latent_w = latents.shape
310
 
311
- # Busca os fatores de escala do VAE. Assumimos que o fator temporal e espacial são iguais.
312
- # Esta é uma suposição segura para o LTX-Video.
313
- video_scale_factor = getattr(self.vae, 'temporal_downscale_factor', 8)
314
- vae_scale_factor = getattr(self.vae, 'spatial_downscale_factor', 8)
315
 
316
- # Converte as dimensões latentes para as dimensões de pixel correspondentes.
317
  pixel_height = latent_h * vae_scale_factor
318
  pixel_width = latent_w * vae_scale_factor
319
-
320
- # --- [A CORREÇÃO PRINCIPAL ESTÁ AQUI] ---
321
- # Para que a pipeline espere um latente com 'num_latent_frames', precisamos
322
- # fornecer um número de frames de pixel que, após a divisão e a adição de 1
323
- # (devido ao VAE causal), resulte no número original de frames latentes.
324
- # A fórmula inversa é: (num_latent_frames - 1) * video_scale_factor
325
  pixel_frames = (num_latent_frames - 1) * video_scale_factor
326
 
327
- # Chama o ltx_manager com os parâmetros corretos.
328
  refined_latents_tensor, _ = self.ltx_manager.refine_latents(
329
  latents,
330
  height=pixel_height,
@@ -339,9 +287,7 @@ class Deformes4DEngine:
339
 
340
  logger.info(f"Retornando tensor latente refinado com shape: {refined_latents_tensor.shape}")
341
  return refined_latents_tensor
342
-
343
 
344
-
345
  def upscale_latents(self, latents: torch.Tensor) -> torch.Tensor:
346
  """Interface para o UpscalerSpecialist."""
347
  logger.info(f"Realizando upscale em tensor latente com shape {latents.shape}.")
@@ -349,7 +295,7 @@ class Deformes4DEngine:
349
 
350
  def _generate_latent_tensor_internal(self, conditioning_items, ltx_params, target_resolution, total_frames_to_generate):
351
  kwargs = {
352
- **ltx_params, 'width': target_resolution[0], 'height': target_resolution[1],
353
  'video_total_frames': total_frames_to_generate, 'video_fps': 24,
354
  'current_fragment_index': int(time.time()), 'conditioning_items_data': conditioning_items
355
  }
 
26
  from ltx_manager_helpers import ltx_manager_singleton
27
  from gemini_helpers import gemini_singleton
28
  from upscaler_specialist import upscaler_specialist_singleton
29
+ from hd_specialist import hd_specialist_singleton
30
  from ltx_video.models.autoencoders.causal_video_autoencoder import CausalVideoAutoencoder
31
  from ltx_video.models.autoencoders.vae_encode import vae_encode, vae_decode
32
 
 
58
  self._vae.to(self.device); self._vae.eval()
59
  return self._vae
60
 
61
+ # --- MÉTODOS AUXILIARES ---
 
 
62
 
63
+ def _extract_audio_ffmpeg(self, video_path: str, output_audio_path: str) -> str | None:
64
+ """Extrai a trilha sonora de um vídeo para uso posterior."""
65
+ if not os.path.exists(video_path): return None
66
+ cmd = ['ffmpeg', '-y', '-i', video_path, '-vn', '-acodec', 'copy', output_audio_path]
67
+ try:
68
+ subprocess.run(cmd, check=True, capture_output=True, text=True, encoding='utf-8')
69
+ logger.info(f"Áudio extraído com sucesso para {output_audio_path}")
70
+ return output_audio_path
71
+ except subprocess.CalledProcessError:
72
+ logger.warning(f"Não foi possível extrair o áudio de {os.path.basename(video_path)}. O vídeo pode ser silencioso.")
73
+ return None
74
 
75
+ def _combine_video_and_audio_ffmpeg(self, video_path: str, audio_path: str, output_path: str):
76
+ """Combina um vídeo (sem som) com um arquivo de áudio."""
77
+ cmd = ['ffmpeg', '-y', '-i', video_path, '-i', audio_path, '-c:v', 'copy', '-c:a', 'aac', '-shortest', output_path]
78
+ try:
79
+ subprocess.run(cmd, check=True, capture_output=True, text=True, encoding='utf-8')
80
+ logger.info(f"Áudio e vídeo combinados com sucesso em {output_path}")
81
+ except subprocess.CalledProcessError as e:
82
+ raise gr.Error(f"Falha ao combinar áudio e vídeo: {e.stderr}")
83
 
84
  @torch.no_grad()
85
  def latents_to_pixels(self, latent_tensor: torch.Tensor, decode_timestep: float = 0.05) -> torch.Tensor:
 
87
  timestep_tensor = torch.tensor([decode_timestep] * latent_tensor.shape[0], device=self.device, dtype=latent_tensor.dtype)
88
  return vae_decode(latent_tensor, self.vae, is_video=True, timestep=timestep_tensor, vae_per_channel_normalize=True)
89
 
 
 
 
 
 
 
 
 
90
  def _preprocess_image_for_latent_conversion(self, image: Image.Image, target_resolution: tuple) -> Image.Image:
91
  if image.size != target_resolution:
92
  return ImageOps.fit(image, target_resolution, Image.Resampling.LANCZOS)
 
98
  tensor = (tensor * 2.0) - 1.0
99
  return self.pixels_to_latents(tensor)
100
 
101
+ @torch.no_grad()
102
+ def pixels_to_latents(self, tensor: torch.Tensor) -> torch.Tensor:
103
+ tensor = tensor.to(self.device, dtype=self.vae.dtype)
104
+ return vae_encode(tensor, self.vae, vae_per_channel_normalize=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
 
106
+ # --- NÚCLEO DA LÓGICA ADUC-SDR ---
107
  def generate_full_movie(self, keyframes: list, global_prompt: str, storyboard: list,
108
  seconds_per_fragment: float, trim_percent: int,
109
  handler_strength: float, destination_convergence_strength: float,
110
  video_resolution: int, use_continuity_director: bool,
111
  progress: gr.Progress = gr.Progress()):
112
 
113
+ num_transitions_to_generate = len(keyframes) - 1
114
+ TOTAL_STEPS = num_transitions_to_generate + 4 # Fragmentos + etapas de pós-produção
115
  current_step = 0
116
+
117
  FPS = 24
118
  FRAMES_PER_LATENT_CHUNK = 8
119
  ECO_LATENT_CHUNKS = 2
120
 
121
  total_frames_brutos = self._quantize_to_multiple(int(seconds_per_fragment * FPS), FRAMES_PER_LATENT_CHUNK)
 
122
  frames_a_podar = self._quantize_to_multiple(int(total_frames_brutos * (trim_percent / 100)), FRAMES_PER_LATENT_CHUNK)
123
  latents_a_podar = frames_a_podar // FRAMES_PER_LATENT_CHUNK
124
 
125
+ if total_frames_brutos // FRAMES_PER_LATENT_CHUNK <= latents_a_podar + 1:
126
+ raise gr.Error("A combinação de duração e poda é muito agressiva.")
127
 
128
  DEJAVU_FRAME_TARGET = frames_a_podar - 1 if frames_a_podar > 0 else 0
129
  DESTINATION_FRAME_TARGET = total_frames_brutos - 1
 
134
 
135
  eco_latent_for_next_loop = None
136
  dejavu_latent_for_next_loop = None
 
 
137
  processed_latent_fragments = []
138
 
139
+ # --- ATO I: GERAÇÃO LATENTE (LOOP DE FRAGMENTOS) ---
140
  for i in range(num_transitions_to_generate):
141
  fragment_index = i + 1
142
  current_step += 1
143
  progress(current_step / TOTAL_STEPS, desc=f"Gerando Fragmento {fragment_index}/{num_transitions_to_generate}")
144
 
 
145
  past_keyframe_path = keyframe_paths[i - 1] if i > 0 else keyframe_paths[i]
146
  start_keyframe_path = keyframe_paths[i]
147
  destination_keyframe_path = keyframe_paths[i + 1]
 
152
  transition_type, motion_prompt = decision["transition_type"], decision["motion_prompt"]
153
  story_history += f"\n- Ato {fragment_index}: {motion_prompt}"
154
 
155
+ expected_height, expected_width = video_resolution, video_resolution
156
  downscale_factor = 2 / 3
157
  downscaled_height = self._quantize_to_multiple(int(expected_height * downscale_factor), 8)
158
  downscaled_width = self._quantize_to_multiple(int(expected_width * downscale_factor), 8)
 
180
  if transition_type == "cut":
181
  eco_latent_for_next_loop, dejavu_latent_for_next_loop = None, None
182
 
 
183
  upscaled_latents = self.upscale_latents(latents_video)
184
  refined_latents = self.refine_latents(upscaled_latents, motion_prompt=f"refining scene: {motion_prompt}")
185
  processed_latent_fragments.append(refined_latents)
 
 
 
 
 
 
 
 
 
186
 
187
+ # --- ATO II: RENDERIZAÇÃO PRIMÁRIA (COM CORREÇÃO DE OOM) ---
188
+ base_name = f"movie_{int(time.time())}"
189
  current_step += 1
190
+ progress(current_step / TOTAL_STEPS, desc="Renderizando vídeo (em lotes)...")
191
  refined_silent_video_path = os.path.join(self.workspace_dir, f"{base_name}_refined_silent.mp4")
192
+
193
+ with imageio.get_writer(refined_silent_video_path, fps=FPS, codec='libx264', quality=8, output_params=['-pix_fmt', 'yuv420p']) as writer:
194
+ for i, latent_fragment in enumerate(processed_latent_fragments):
195
+ logger.info(f"Decodificando fragmento {i+1}/{len(processed_latent_fragments)} para pixels...")
196
+ pixel_tensor_fragment = self.latents_to_pixels(latent_fragment)
197
+
198
+ pixel_tensor_fragment = pixel_tensor_fragment.squeeze(0).permute(1, 2, 3, 0)
199
+ pixel_tensor_fragment = (pixel_tensor_fragment.clamp(-1, 1) + 1) / 2.0
200
+ video_np_fragment = (pixel_tensor_fragment.detach().cpu().float().numpy() * 255).astype(np.uint8)
201
+
202
+ for frame in video_np_fragment:
203
+ writer.append_data(frame)
204
+
205
+ del pixel_tensor_fragment, video_np_fragment
206
+ gc.collect()
207
+ torch.cuda.empty_cache()
208
 
209
+ logger.info(f"Vídeo base renderizado com sucesso em: {refined_silent_video_path}")
210
+ del processed_latent_fragments
211
  gc.collect()
212
  torch.cuda.empty_cache()
213
 
214
+ # --- ATO III: MASTERIZAÇÃO FINAL (ÁUDIO E HD) ---
215
+ current_step += 1
216
+ progress(current_step / TOTAL_STEPS, desc="Gerando trilha sonora...")
217
+ try:
218
+ video_with_audio_path = audio_specialist_singleton.generate_audio_for_video(
219
+ video_path=refined_silent_video_path,
220
+ prompt=global_prompt,
221
+ duration_seconds=self._get_video_duration(refined_silent_video_path)
222
+ )
223
+ temp_audio_path = os.path.join(self.workspace_dir, f"{base_name}_extracted_audio.aac")
224
+ extracted_audio_path = self._extract_audio_ffmpeg(video_with_audio_path, temp_audio_path)
225
+ except Exception as e:
226
+ logger.error(f"Falha na geração de áudio: {e}. O vídeo final será silencioso.")
227
+ extracted_audio_path = None
228
+
229
  current_step += 1
230
  progress(current_step / TOTAL_STEPS, desc="Aprimoramento final (HD)...")
231
  hq_silent_video_path = os.path.join(self.workspace_dir, f"{base_name}_hq_silent.mp4")
232
  try:
 
233
  hd_specialist_singleton.process_video(
234
  input_video_path=refined_silent_video_path,
235
  output_video_path=hq_silent_video_path,
236
  prompt=global_prompt
237
  )
238
  except Exception as e:
239
+ logger.error(f"Falha no aprimoramento HD: {e}. Usando vídeo de qualidade padrão.")
 
240
  os.rename(refined_silent_video_path, hq_silent_video_path)
241
 
242
  current_step += 1
243
  progress(current_step / TOTAL_STEPS, desc="Finalizando montagem...")
244
  final_video_path = os.path.join(self.workspace_dir, f"{base_name}_FINAL.mp4")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
 
246
+ if extracted_audio_path and os.path.exists(hq_silent_video_path):
247
+ self._combine_video_and_audio_ffmpeg(hq_silent_video_path, extracted_audio_path, final_video_path)
248
+ else:
249
+ os.rename(hq_silent_video_path, final_video_path)
250
 
251
+ logger.info(f"Processo concluído! Vídeo final salvo em: {final_video_path}")
252
+ yield {"final_path": final_video_path}
253
 
254
  def refine_latents(self, latents: torch.Tensor,
255
  fps: int = 24,
 
262
  """
263
  logger.info(f"Refinando tensor latente com shape {latents.shape} para refinamento.")
264
 
 
265
  _, _, num_latent_frames, latent_h, latent_w = latents.shape
266
 
267
+ video_scale_factor = getattr(self.vae.config, 'temporal_scale_factor', 8)
268
+ vae_scale_factor = getattr(self.vae.config, 'spatial_downscale_factor', 8)
 
 
269
 
 
270
  pixel_height = latent_h * vae_scale_factor
271
  pixel_width = latent_w * vae_scale_factor
272
+
273
+ # A fórmula inversa para o VAE causal: (N_latente - 1) * FatorDeEscala
 
 
 
 
274
  pixel_frames = (num_latent_frames - 1) * video_scale_factor
275
 
 
276
  refined_latents_tensor, _ = self.ltx_manager.refine_latents(
277
  latents,
278
  height=pixel_height,
 
287
 
288
  logger.info(f"Retornando tensor latente refinado com shape: {refined_latents_tensor.shape}")
289
  return refined_latents_tensor
 
290
 
 
291
  def upscale_latents(self, latents: torch.Tensor) -> torch.Tensor:
292
  """Interface para o UpscalerSpecialist."""
293
  logger.info(f"Realizando upscale em tensor latente com shape {latents.shape}.")
 
295
 
296
  def _generate_latent_tensor_internal(self, conditioning_items, ltx_params, target_resolution, total_frames_to_generate):
297
  kwargs = {
298
+ **ltx_params, 'width': target_resolution[1], 'height': target_resolution[0],
299
  'video_total_frames': total_frames_to_generate, 'video_fps': 24,
300
  'current_fragment_index': int(time.time()), 'conditioning_items_data': conditioning_items
301
  }