euiia commited on
Commit
9cdf9d7
·
verified ·
1 Parent(s): 63ceaa5

Update deformes4D_engine.py

Browse files
Files changed (1) hide show
  1. deformes4D_engine.py +124 -133
deformes4D_engine.py CHANGED
@@ -26,6 +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 ltx_video.models.autoencoders.causal_video_autoencoder import CausalVideoAutoencoder
30
  from ltx_video.models.autoencoders.vae_encode import vae_encode, vae_decode
31
 
@@ -80,7 +81,7 @@ class Deformes4DEngine:
80
  video_tensor = video_tensor.squeeze(0).permute(1, 2, 3, 0)
81
  video_tensor = (video_tensor.clamp(-1, 1) + 1) / 2.0
82
  video_np = (video_tensor.detach().cpu().float().numpy() * 255).astype(np.uint8)
83
- with imageio.get_writer(path, fps=fps, codec='libx264', quality=8) as writer:
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:
@@ -94,56 +95,48 @@ class Deformes4DEngine:
94
  tensor = (tensor * 2.0) - 1.0
95
  return self.pixels_to_latents(tensor)
96
 
97
- def _get_video_frame_count(self, video_path: str) -> int | None:
98
- if not os.path.exists(video_path): return None
99
- cmd = ['ffprobe', '-v', 'error', '-select_streams', 'v:0', '-count_frames',
100
- '-show_entries', 'stream=nb_read_frames', '-of', 'default=nokey=1:noprint_wrappers=1', video_path]
101
- try:
102
- result = subprocess.run(cmd, check=True, capture_output=True, text=True, encoding='utf-8')
103
- return int(result.stdout.strip())
104
- except Exception: return None
105
-
106
- def _trim_last_frame_ffmpeg(self, input_path: str, output_path: str) -> bool:
107
- frame_count = self._get_video_frame_count(input_path)
108
- if frame_count is None or frame_count < 2:
109
- if os.path.exists(input_path): os.rename(input_path, output_path)
110
- return True
111
- vf_filter = f"select='lt(n,{frame_count - 1})',setpts=PTS-STARTPTS"
112
- cmd_list = ['ffmpeg', '-y', '-i', input_path, '-vf', vf_filter, '-an', output_path]
113
- try:
114
- subprocess.run(cmd_list, check=True, capture_output=True, text=True, encoding='utf-8')
115
- return True
116
- except subprocess.CalledProcessError: return False
117
-
118
- def concatenate_videos_ffmpeg(self, video_paths: list[str], output_path: str) -> str:
119
- if not video_paths: raise gr.Error("Nenhum fragmento de vídeo para montar.")
120
- list_file_path = os.path.join(self.workspace_dir, "concat_list.txt")
121
- with open(list_file_path, 'w', encoding='utf-8') as f:
122
- for path in video_paths: f.write(f"file '{os.path.abspath(path)}'\n")
123
- cmd_list = ['ffmpeg', '-y', '-f', 'concat', '-safe', '0', '-i', list_file_path, '-c', 'copy', output_path]
124
- try:
125
- subprocess.run(cmd_list, check=True, capture_output=True, text=True)
126
- except subprocess.CalledProcessError as e:
127
- raise gr.Error(f"Falha na montagem final do vídeo. Detalhes: {e.stderr}")
128
- return output_path
129
-
130
-
131
-
132
- def _generate_video_and_audio(self, silent_video_path: str, audio_prompt: str, base_name: str) -> str:
133
  try:
134
  result = subprocess.run(
135
- ["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", silent_video_path],
136
  capture_output=True, text=True, check=True)
137
- duration = float(result.stdout.strip())
138
  except Exception:
139
- frame_count = self._get_video_frame_count(silent_video_path)
140
- duration = (frame_count / 24.0) if frame_count else 0
141
-
142
- video_with_audio_path = audio_specialist_singleton.generate_audio_for_video(
143
- video_path=silent_video_path, prompt=audio_prompt,
144
- duration_seconds=duration)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
- return video_with_audio_path
 
 
 
 
 
 
 
147
 
148
  # NÚCLEO DA LÓGICA ADUC-SDR
149
  def generate_full_movie(self, keyframes: list, global_prompt: str, storyboard: list,
@@ -152,6 +145,9 @@ class Deformes4DEngine:
152
  video_resolution: int, use_continuity_director: bool,
153
  progress: gr.Progress = gr.Progress()):
154
 
 
 
 
155
  FPS = 24
156
  FRAMES_PER_LATENT_CHUNK = 8
157
  ECO_LATENT_CHUNKS = 2
@@ -171,18 +167,18 @@ class Deformes4DEngine:
171
  keyframe_paths = [item[0] if isinstance(item, tuple) else item for item in keyframes]
172
  story_history = ""
173
 
174
-
175
  eco_latent_for_next_loop = None
176
  dejavu_latent_for_next_loop = None
177
 
178
  num_transitions_to_generate = len(keyframe_paths) - 1
179
- upscaled_latent_fragments = []
180
 
181
-
182
  for i in range(num_transitions_to_generate):
183
  fragment_index = i + 1
184
- progress(i / (num_transitions_to_generate + 2), desc=f"Gerando Latentes do Fragmento {fragment_index}")
 
185
 
 
186
  past_keyframe_path = keyframe_paths[i - 1] if i > 0 else keyframe_paths[i]
187
  start_keyframe_path = keyframe_paths[i]
188
  destination_keyframe_path = keyframe_paths[i + 1]
@@ -193,16 +189,12 @@ class Deformes4DEngine:
193
  transition_type, motion_prompt = decision["transition_type"], decision["motion_prompt"]
194
  story_history += f"\n- Ato {fragment_index}: {motion_prompt}"
195
 
196
-
197
  expected_height, expected_width = 768, 1152
198
  downscale_factor = 2 / 3
199
  downscaled_height = self._quantize_to_multiple(int(expected_height * downscale_factor), 8)
200
  downscaled_width = self._quantize_to_multiple(int(expected_width * downscale_factor), 8)
201
  target_resolution_tuple = (downscaled_height, downscaled_width)
202
- final_resolution_tuple = (expected_height, expected_width)
203
-
204
 
205
-
206
  conditioning_items = []
207
  if eco_latent_for_next_loop is None:
208
  img_start = self._preprocess_image_for_latent_conversion(Image.open(start_keyframe_path).convert("RGB"), target_resolution_tuple)
@@ -225,106 +217,105 @@ class Deformes4DEngine:
225
  if transition_type == "cut":
226
  eco_latent_for_next_loop, dejavu_latent_for_next_loop = None, None
227
 
 
 
 
 
228
 
229
- latent_upscale_fragment = self.upscale_latents(latents_video)
230
-
231
- list_latents_fragments.append(latent_upscale_fragment)
232
-
233
 
234
-
235
-
236
- progress((num_transitions_to_generate) / (num_transitions_to_generate + 2), desc="Concatenando latentes...")
237
- tensors_para_concatenar = []
238
- target_device = self.device
239
- for idx, tensor_frag in enumerate(upscaled_latent_fragments):
240
- tensor_on_target_device = tensor_frag.to(target_device)
241
- if idx < len(upscaled_latent_fragments) - 1:
242
- tensors_para_concatenar.append(tensor_on_target_device[:, :, :-1, :, :])
243
- else:
244
- tensors_para_concatenar.append(tensor_on_target_device)
245
-
246
  final_concatenated_latents = torch.cat(tensors_para_concatenar, dim=2)
247
-
248
- progress((num_transitions_to_generate + 1) / (num_transitions_to_generate + 2), desc="Pós-produção (Upscale e Refinamento)...")
249
- base_name = f"final_movie_hq_{int(time.time())}"
250
-
251
-
252
- # Pós-produção: Upscale + Refine
253
- #high_quality_video_path = self._render_and_post_process(
254
- # final_concatenated_latents,
255
- # base_name=base_name,
256
- # expected_height=720,
257
- # expected_width=720,
258
- # fps=24
259
- #)
260
-
261
-
262
- video_path = os.path.join(self.workspace_dir, f"{base_name}_HQ.mp4")
263
-
264
- final_pixel_tensor = self.latents_to_pixels(final_concatenated_latents)
265
- self.save_video_from_tensor(final_pixel_tensor, video_path, fps=24)
266
- logger.info(f"Vídeo final salvo em: {video_path}")
267
 
 
268
 
269
- #progress((num_transitions_to_generate + 1.5) / (num_transitions_to_generate + 2), desc="Gerando paisagem sonora...")
270
- #video_with_audio_path = self._generate_video_and_audio(
271
- # silent_video_path=final_concatenated_latents,
272
- # audio_prompt=global_prompt,
273
- # base_name=base_name
274
- #)
275
 
276
- yield {"final_path": video_path}
277
-
278
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
 
280
  def refine_latents(self, latents: torch.Tensor,
281
  fps: int = 24,
282
  denoise_strength: float = 0.35,
283
  refine_steps: int = 12,
284
  motion_prompt: str = "refining video, improving details, cinematic quality") -> torch.Tensor:
285
- """
286
- Aplica um passe de refinamento (denoise) em um tensor latente.
287
- """
288
- logger.info(f"Recebido tensor latente com shape {latents.shape} para refinamento.")
289
-
290
- # Extrai as dimensões do tensor latente.
291
  _, _, num_frames, latent_h, latent_w = latents.shape
292
-
293
- # Obtém o fator de escala do VAE (geralmente 8).
294
  vae_scale_factor = self.vae.config.scaling_factor if hasattr(self.vae.config, 'scaling_factor') else 8
295
-
296
- # Calcula as dimensões em PIXELS correspondentes.
297
- pixel_height = latent_h * vae_scale_factor
298
- pixel_width = latent_w * vae_scale_factor
299
 
300
- # Chama o ltx_manager com os parâmetros corretos para evitar o AssertionError.
301
  refined_latents_tensor, _ = self.ltx_manager.refine_latents(
302
- latents,
303
- height=pixel_height,
304
- width=pixel_width,
305
- video_total_frames=num_frames,
306
- video_fps=fps,
307
- motion_prompt=motion_prompt,
308
- current_fragment_index=int(time.time()),
309
- denoise_strength=denoise_strength,
310
- refine_steps=refine_steps
311
- )
312
 
313
- logger.info(f"Retornando tensor latente refinado com shape: {refined_latents_tensor.shape}")
314
  return refined_latents_tensor
315
-
316
 
317
  def upscale_latents(self, latents: torch.Tensor) -> torch.Tensor:
318
- """
319
- Recebe um tensor latente de baixa resolução e retorna a versão 2x upscaled.
320
- Este método atua como uma interface para o UpscalerSpecialist.
321
- """
322
- logger.info(f"Recebido tensor latente com shape {latents.shape} para upscale.")
323
- upscaled_latents = upscaler_specialist_singleton.upscale(latents)
324
- logger.info(f"Retornando tensor latente upscaled com novo shape: {upscaled_latents.shape}")
325
- return upscaled_latents
326
 
327
-
328
  def _generate_latent_tensor_internal(self, conditioning_items, ltx_params, target_resolution, total_frames_to_generate):
329
  kwargs = {
330
  **ltx_params, 'width': target_resolution[1], 'height': target_resolution[0],
 
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
 
 
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:
 
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,
 
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
 
167
  keyframe_paths = [item[0] if isinstance(item, tuple) else item for item in keyframes]
168
  story_history = ""
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
  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)
196
  target_resolution_tuple = (downscaled_height, downscaled_width)
 
 
197
 
 
198
  conditioning_items = []
199
  if eco_latent_for_next_loop is None:
200
  img_start = self._preprocess_image_for_latent_conversion(Image.open(start_keyframe_path).convert("RGB"), target_resolution_tuple)
 
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
+ # --- ATO II: PRIMEIRA MASTERIZAÇÃO (VÍDEO + MÚSICA) ---
246
+ current_step += 1
247
+ progress(current_step / TOTAL_STEPS, desc="Gerando trilha sonora...")
248
+ try:
249
+ # Assume que seu audio_specialist tem um método que retorna o caminho do áudio
250
+ audio_path = audio_specialist_singleton.generate_standalone_audio(
251
+ prompt=global_prompt,
252
+ duration_seconds=self._get_video_duration(refined_silent_video_path),
253
+ output_dir=self.workspace_dir
254
+ )
255
+
256
+ refined_video_with_audio_path = os.path.join(self.workspace_dir, f"{base_name}_refined_with_audio.mp4")
257
+ self._combine_video_and_audio_ffmpeg(refined_silent_video_path, audio_path, refined_video_with_audio_path)
258
+ logger.info(f"Primeira masterização com áudio salva em: {refined_video_with_audio_path}")
259
+ # Você pode opcionalmente retornar este vídeo aqui como uma prévia
260
+ # yield {"preview_path": refined_video_with_audio_path}
261
+ except Exception as e:
262
+ logger.error(f"Falha na geração ou combinação de áudio: {e}. Prosseguindo sem áudio.")
263
+ audio_path = None # Garante que a variável exista
264
+ refined_video_with_audio_path = refined_silent_video_path # Usa o vídeo silencioso como fallback
265
+
266
+ # --- ATO III: MASTERIZAÇÃO FINAL (APLICAÇÃO DE HD) ---
267
+ current_step += 1
268
+ progress(current_step / TOTAL_STEPS, desc="Aprimoramento final (HD)...")
269
+ hq_silent_video_path = os.path.join(self.workspace_dir, f"{base_name}_hq_silent.mp4")
270
+ try:
271
+ # O Especialista HD processa o vídeo silencioso refinado
272
+ hd_specialist_singleton.process_video(
273
+ input_video_path=refined_silent_video_path,
274
+ output_video_path=hq_silent_video_path,
275
+ prompt=global_prompt
276
+ )
277
+ except Exception as e:
278
+ logger.error(f"Falha no processo de aprimoramento HD. Usando o vídeo refinado como fallback. Erro: {e}")
279
+ # Se o HD falhar, usamos o vídeo refinado (silencioso) como base para o final
280
+ os.rename(refined_silent_video_path, hq_silent_video_path)
281
+
282
+ current_step += 1
283
+ progress(current_step / TOTAL_STEPS, desc="Finalizando montagem...")
284
+ final_video_path = os.path.join(self.workspace_dir, f"{base_name}_FINAL.mp4")
285
+
286
+ #if audio_path and os.path.exists(audio_path):
287
+ # # Se o áudio foi gerado, combina o vídeo de ALTA QUALIDADE com ele
288
+ # self._combine_video_and_audio_ffmpeg(hq_silent_video_path, audio_path, final_video_path)
289
+ #else:
290
+ # # Se não houver áudio, apenas renomeia o vídeo de alta qualidade
291
+ # os.rename(hq_silent_video_path, final_video_path)
292
+
293
+ logger.info(f"Processo concluído! Vídeo final salvo em: {hq_silent_video_path}")
294
+ yield {"final_path": final_video_path}
295
 
296
  def refine_latents(self, latents: torch.Tensor,
297
  fps: int = 24,
298
  denoise_strength: float = 0.35,
299
  refine_steps: int = 12,
300
  motion_prompt: str = "refining video, improving details, cinematic quality") -> torch.Tensor:
301
+ """Aplica um passe de refinamento (denoise) em um tensor latente."""
302
+ logger.info(f"Refinando tensor latente com shape {latents.shape}.")
 
 
 
 
303
  _, _, num_frames, latent_h, latent_w = latents.shape
 
 
304
  vae_scale_factor = self.vae.config.scaling_factor if hasattr(self.vae.config, 'scaling_factor') else 8
305
+ pixel_height, pixel_width = latent_h * vae_scale_factor, latent_w * vae_scale_factor
 
 
 
306
 
 
307
  refined_latents_tensor, _ = self.ltx_manager.refine_latents(
308
+ latents, height=pixel_height, width=pixel_width, video_total_frames=num_frames,
309
+ video_fps=fps, motion_prompt=motion_prompt, current_fragment_index=int(time.time()),
310
+ denoise_strength=denoise_strength, refine_steps=refine_steps)
 
 
 
 
 
 
 
311
 
 
312
  return refined_latents_tensor
 
313
 
314
  def upscale_latents(self, latents: torch.Tensor) -> torch.Tensor:
315
+ """Interface para o UpscalerSpecialist."""
316
+ logger.info(f"Realizando upscale em tensor latente com shape {latents.shape}.")
317
+ return upscaler_specialist_singleton.upscale(latents)
 
 
 
 
 
318
 
 
319
  def _generate_latent_tensor_internal(self, conditioning_items, ltx_params, target_resolution, total_frames_to_generate):
320
  kwargs = {
321
  **ltx_params, 'width': target_resolution[1], 'height': target_resolution[0],