euiia commited on
Commit
1b8ed0f
·
verified ·
1 Parent(s): 21b2ae3

Update deformes4D_engine.py

Browse files
Files changed (1) hide show
  1. deformes4D_engine.py +124 -44
deformes4D_engine.py CHANGED
@@ -20,6 +20,7 @@ from dataclasses import dataclass
20
  import gradio as gr
21
  import subprocess
22
  import gc
 
23
 
24
  from ltx_manager_helpers import ltx_manager_singleton
25
  from gemini_helpers import gemini_singleton
@@ -49,6 +50,9 @@ class Deformes4DEngine:
49
  self._vae = None
50
  self.device = 'cuda' if torch.cuda.is_available() else 'cpu'
51
  logger.info("Especialista Deformes4D (Executor ADUC-SDR) inicializado.")
 
 
 
52
 
53
  @property
54
  def vae(self):
@@ -93,13 +97,22 @@ class Deformes4DEngine:
93
  list_file_path = os.path.join(self.workspace_dir, "concat_list.txt")
94
  with open(list_file_path, 'w', encoding='utf-8') as f:
95
  for path in video_paths: f.write(f"file '{os.path.abspath(path)}'\n")
96
- cmd_list = ['ffmpeg', '-y', '-f', 'concat', '-safe', '0', '-i', list_file_path, '-c', 'copy', output_path]
 
 
97
  logger.info(f"Concatenando {len(video_paths)} clipes de vídeo em {output_path}...")
98
  try:
99
  subprocess.run(cmd_list, check=True, capture_output=True, text=True)
100
  except subprocess.CalledProcessError as e:
101
  logger.error(f"Erro no FFmpeg: {e.stderr}")
102
- raise gr.Error(f"Falha na montagem final do vídeo. Detalhes: {e.stderr}")
 
 
 
 
 
 
 
103
 
104
  # --- NÚCLEO DA LÓGICA ADUC-SDR ---
105
  def generate_full_movie(self, keyframes: list, global_prompt: str, storyboard: list,
@@ -109,10 +122,18 @@ class Deformes4DEngine:
109
  video_resolution: int, use_continuity_director: bool,
110
  progress: gr.Progress = gr.Progress()):
111
 
 
112
  FPS = 24
113
  FRAMES_PER_LATENT_CHUNK = 8
114
  ECO_LATENT_CHUNKS = 2
 
115
 
 
 
 
 
 
 
116
  total_frames_brutos = self._quantize_to_multiple(int(seconds_per_fragment * FPS), FRAMES_PER_LATENT_CHUNK)
117
  frames_a_podar = self._quantize_to_multiple(int(total_frames_brutos * (trim_percent / 100)), FRAMES_PER_LATENT_CHUNK)
118
  latents_a_podar = frames_a_podar // FRAMES_PER_LATENT_CHUNK
@@ -128,17 +149,19 @@ class Deformes4DEngine:
128
  target_resolution_tuple = (video_resolution, video_resolution)
129
 
130
  eco_latent_for_next_loop, dejavu_latent_for_next_loop = None, None
131
- latent_fragments, latent_fragment_lengths = [], []
132
 
133
  if len(keyframe_paths) < 2: raise gr.Error(f"A geração requer no mínimo 2 keyframes. Você forneceu {len(keyframe_paths)}.")
134
 
135
  num_transitions_to_generate = len(keyframe_paths) - 1
136
 
 
 
137
  for i in range(num_transitions_to_generate):
138
  fragment_index = i + 1
139
- progress(i / num_transitions_to_generate, desc=f"Gerando Latentes {fragment_index}/{num_transitions_to_generate}")
140
 
141
- # ... (Lógica de decisão do Gemini e preparação de âncoras) ...
142
  past_keyframe_path = keyframe_paths[i - 1] if i > 0 else keyframe_paths[i]
143
  start_keyframe_path = keyframe_paths[i]
144
  destination_keyframe_path = keyframe_paths[i + 1]
@@ -172,57 +195,110 @@ class Deformes4DEngine:
172
  if transition_type == "cut":
173
  eco_latent_for_next_loop, dejavu_latent_for_next_loop = None, None
174
 
175
- if use_upscaler:
176
- # [REATORADO] Chamada para o novo especialista
177
- latents_video = latent_enhancer_specialist_singleton.upscale(latents_video)
178
-
179
- latent_fragments.append(latents_video)
180
- latent_fragment_lengths.append(latents_video.shape[2])
 
 
181
 
182
  del eco_latent_for_next_loop, dejavu_latent_for_next_loop
183
  gc.collect(); torch.cuda.empty_cache()
184
 
185
- logger.info("--- CONCATENANDO E REFINANDO SUPER-LATENTE ---")
186
- tensors_para_concatenar = [frag.to(self.device)[:, :, :-1, :, :] if i < len(latent_fragments) - 1 else frag.to(self.device) for i, frag in enumerate(latent_fragments)]
187
- del latent_fragments; gc.collect(); torch.cuda.empty_cache()
 
 
 
 
 
 
 
 
188
 
189
- processed_latents = torch.cat(tensors_para_concatenar, dim=2)
190
- del tensors_para_concatenar; gc.collect(); torch.cuda.empty_cache()
191
 
192
- logger.info(f"Concatenação concluída. Shape do super-latente: {processed_latents.shape}")
 
 
 
 
 
 
 
193
 
194
- base_name = f"fragment_{i}_{int(time.time())}"
195
- current_path = os.path.join(self.workspace_dir, f"{base_name}_temp.mp4")
196
 
197
- if use_audio:
198
- current_path = self._generate_video_and_audio_from_latents(processed_latents, " ", base_name)
199
- else:
200
- current_path = self.latents_to_pixels(processed_latents)
201
- self.save_video_from_tensor(pixel_tensor, current_path, fps=24)
202
- del pixel_tensor
203
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
 
205
- if use_hd:
206
- current_path = os.path.join(self.workspace_dir, f"{base_name}_hd.mp4")
207
- try:
208
- hd_specialist_singleton.process_video(input_video_path=current_path, output_video_path=current_path, prompt=" ")
209
- except Exception as e:
210
- logger.error(f"Falha na masterização HD do fragmento {i+1}: {e}. Usando versão padrão.")
211
-
212
  #if use_refiner:
213
  # progress(0.8, desc="Refinando continuidade visual...")
214
  # # [REATORADO] Chamada para o novo especialista
215
- #
216
-
217
-
 
 
 
218
 
219
- progress(0.98, desc="Montagem final...")
220
-
221
- logger.info(f"Processo concluído! Vídeo final salvo em: {current_path}")
222
- yield {"final_path": current_path}
 
 
 
 
 
 
 
 
 
223
 
224
  def _generate_video_and_audio_from_latents(self, latent_tensor, audio_prompt, base_name):
225
- silent_video_path = os.path.join(self.workspace_dir, f"{base_name}_silent_for_audio.mp4")
 
 
 
226
  pixel_tensor = self.latents_to_pixels(latent_tensor)
227
  self.save_video_from_tensor(pixel_tensor, silent_video_path, fps=24)
228
  del pixel_tensor; gc.collect(); torch.cuda.empty_cache()
@@ -233,13 +309,17 @@ class Deformes4DEngine:
233
  capture_output=True, text=True, check=True)
234
  frag_duration = float(result.stdout.strip())
235
  except (subprocess.CalledProcessError, ValueError, FileNotFoundError):
236
- logger.warning(f"ffprobe falhou. Calculando duração manualmente.")
237
- num_pixel_frames = latent_tensor.shape[2] * 8
 
238
  frag_duration = num_pixel_frames / 24.0
239
 
 
240
  video_with_audio_path = audio_specialist_singleton.generate_audio_for_video(
241
  video_path=silent_video_path, prompt=audio_prompt,
242
- duration_seconds=frag_duration)
 
 
243
 
244
  if os.path.exists(silent_video_path):
245
  os.remove(silent_video_path)
 
20
  import gradio as gr
21
  import subprocess
22
  import gc
23
+ import shutil
24
 
25
  from ltx_manager_helpers import ltx_manager_singleton
26
  from gemini_helpers import gemini_singleton
 
50
  self._vae = None
51
  self.device = 'cuda' if torch.cuda.is_available() else 'cpu'
52
  logger.info("Especialista Deformes4D (Executor ADUC-SDR) inicializado.")
53
+ # Cria o diretório de workspace se não existir
54
+ os.makedirs(self.workspace_dir, exist_ok=True)
55
+
56
 
57
  @property
58
  def vae(self):
 
97
  list_file_path = os.path.join(self.workspace_dir, "concat_list.txt")
98
  with open(list_file_path, 'w', encoding='utf-8') as f:
99
  for path in video_paths: f.write(f"file '{os.path.abspath(path)}'\n")
100
+
101
+ # Tenta usar aceleração de hardware (GPU) para a concatenação, se disponível
102
+ cmd_list = ['ffmpeg', '-y', '-hwaccel', 'auto', '-f', 'concat', '-safe', '0', '-i', list_file_path, '-c', 'copy', output_path]
103
  logger.info(f"Concatenando {len(video_paths)} clipes de vídeo em {output_path}...")
104
  try:
105
  subprocess.run(cmd_list, check=True, capture_output=True, text=True)
106
  except subprocess.CalledProcessError as e:
107
  logger.error(f"Erro no FFmpeg: {e.stderr}")
108
+ # Tenta novamente sem aceleração de hardware como fallback
109
+ logger.info("Tentando concatenar novamente sem aceleração de hardware...")
110
+ cmd_list = ['ffmpeg', '-y', '-f', 'concat', '-safe', '0', '-i', list_file_path, '-c', 'copy', output_path]
111
+ try:
112
+ subprocess.run(cmd_list, check=True, capture_output=True, text=True)
113
+ except subprocess.CalledProcessError as e_fallback:
114
+ logger.error(f"Erro no FFmpeg (fallback): {e_fallback.stderr}")
115
+ raise gr.Error(f"Falha na montagem final do vídeo. Detalhes: {e_fallback.stderr}")
116
 
117
  # --- NÚCLEO DA LÓGICA ADUC-SDR ---
118
  def generate_full_movie(self, keyframes: list, global_prompt: str, storyboard: list,
 
122
  video_resolution: int, use_continuity_director: bool,
123
  progress: gr.Progress = gr.Progress()):
124
 
125
+ # --- ETAPA 0: SETUP ---
126
  FPS = 24
127
  FRAMES_PER_LATENT_CHUNK = 8
128
  ECO_LATENT_CHUNKS = 2
129
+ LATENT_PROCESSING_CHUNK_SIZE = 10 # Processa 10 fragmentos latentes por vez para economizar memória
130
 
131
+ run_timestamp = int(time.time())
132
+ temp_latent_dir = os.path.join(self.workspace_dir, f"temp_latents_{run_timestamp}")
133
+ temp_video_clips_dir = os.path.join(self.workspace_dir, f"temp_clips_{run_timestamp}")
134
+ os.makedirs(temp_latent_dir, exist_ok=True)
135
+ os.makedirs(temp_video_clips_dir, exist_ok=True)
136
+
137
  total_frames_brutos = self._quantize_to_multiple(int(seconds_per_fragment * FPS), FRAMES_PER_LATENT_CHUNK)
138
  frames_a_podar = self._quantize_to_multiple(int(total_frames_brutos * (trim_percent / 100)), FRAMES_PER_LATENT_CHUNK)
139
  latents_a_podar = frames_a_podar // FRAMES_PER_LATENT_CHUNK
 
149
  target_resolution_tuple = (video_resolution, video_resolution)
150
 
151
  eco_latent_for_next_loop, dejavu_latent_for_next_loop = None, None
152
+ latent_fragment_paths = [] # Lista para armazenar caminhos dos latentes salvos no disco
153
 
154
  if len(keyframe_paths) < 2: raise gr.Error(f"A geração requer no mínimo 2 keyframes. Você forneceu {len(keyframe_paths)}.")
155
 
156
  num_transitions_to_generate = len(keyframe_paths) - 1
157
 
158
+ # --- ETAPA 1: GERAR FRAGMENTOS LATENTES E SALVAR EM DISCO ---
159
+ logger.info("--- INICIANDO ETAPA 1: Geração de Fragmentos Latentes ---")
160
  for i in range(num_transitions_to_generate):
161
  fragment_index = i + 1
162
+ progress(i / num_transitions_to_generate, desc=f"Gerando Latente {fragment_index}/{num_transitions_to_generate}")
163
 
164
+ # (Lógica de decisão do Gemini e preparação de âncoras - inalterada)
165
  past_keyframe_path = keyframe_paths[i - 1] if i > 0 else keyframe_paths[i]
166
  start_keyframe_path = keyframe_paths[i]
167
  destination_keyframe_path = keyframe_paths[i + 1]
 
195
  if transition_type == "cut":
196
  eco_latent_for_next_loop, dejavu_latent_for_next_loop = None, None
197
 
198
+ # [REATORADO] Mover latente para CPU e salvar no disco para liberar VRAM
199
+ cpu_latent = latents_video.cpu()
200
+ latent_path = os.path.join(temp_latent_dir, f"latent_fragment_{i:04d}.pt")
201
+ torch.save(cpu_latent, latent_path)
202
+ latent_fragment_paths.append(latent_path)
203
+
204
+ del latents_video, cpu_latent
205
+ gc.collect()
206
 
207
  del eco_latent_for_next_loop, dejavu_latent_for_next_loop
208
  gc.collect(); torch.cuda.empty_cache()
209
 
210
+ # --- ETAPA 2: PROCESSAR LATENTES EM LOTES (CHUNKS) ---
211
+ logger.info(f"--- INICIANDO ETAPA 2: Processamento de {len(latent_fragment_paths)} latentes em lotes de {LATENT_PROCESSING_CHUNK_SIZE} ---")
212
+ final_video_clip_paths = []
213
+ num_chunks = -(-len(latent_fragment_paths) // LATENT_PROCESSING_CHUNK_SIZE) # Ceiling division
214
+
215
+ for i in range(num_chunks):
216
+ chunk_start_index = i * LATENT_PROCESSING_CHUNK_SIZE
217
+ chunk_end_index = chunk_start_index + LATENT_PROCESSING_CHUNK_SIZE
218
+ chunk_paths = latent_fragment_paths[chunk_start_index:chunk_end_index]
219
+
220
+ progress(i / num_chunks, desc=f"Processando Lote {i+1}/{num_chunks}")
221
 
222
+ # Carrega os tensores do lote atual do disco para a GPU
223
+ tensors_in_chunk = [torch.load(p, map_location=self.device) for p in chunk_paths]
224
 
225
+ # Concatena os tensores do lote, removendo o latente de sobreposição
226
+ tensors_para_concatenar = [
227
+ frag[:, :, :-1, :, :] if j < len(tensors_in_chunk) - 1 else frag
228
+ for j, frag in enumerate(tensors_in_chunk)
229
+ ]
230
+ sub_group_latent = torch.cat(tensors_para_concatenar, dim=2)
231
+ del tensors_in_chunk, tensors_para_concatenar
232
+ gc.collect(); torch.cuda.empty_cache()
233
 
234
+ logger.info(f"Lote {i+1} concatenado. Shape do sub-latente: {sub_group_latent.shape}")
 
235
 
236
+ # 1. (Opcional) Upscaler Latente
237
+ if use_upscaler:
238
+ logger.info(f"Aplicando Upscaler no lote {i+1}...")
239
+ sub_group_latent = latent_enhancer_specialist_singleton.upscale(sub_group_latent)
240
+ gc.collect(); torch.cuda.empty_cache()
241
+
242
+ # 2. Decodificar Latente para Vídeo (com ou sem áudio)
243
+ base_name = f"clip_{i:04d}_{run_timestamp}"
244
+ current_clip_path = os.path.join(temp_video_clips_dir, f"{base_name}_temp.mp4")
245
+
246
+ if use_audio:
247
+ # O áudio é gerado para o prompt global por enquanto. Pode ser adaptado.
248
+ current_clip_path = self._generate_video_and_audio_from_latents(sub_group_latent, global_prompt, base_name)
249
+ else:
250
+ pixel_tensor = self.latents_to_pixels(sub_group_latent)
251
+ self.save_video_from_tensor(pixel_tensor, current_clip_path, fps=FPS)
252
+ del pixel_tensor
253
+
254
+ del sub_group_latent
255
+ gc.collect(); torch.cuda.empty_cache()
256
+
257
+ # 3. (Opcional) Masterização HD
258
+ if use_hd:
259
+ logger.info(f"Aplicando masterização HD no clipe {i+1}...")
260
+ hd_clip_path = os.path.join(temp_video_clips_dir, f"{base_name}_hd.mp4")
261
+ try:
262
+ hd_specialist_singleton.process_video(input_video_path=current_clip_path, output_video_path=hd_clip_path, prompt=global_prompt)
263
+ # Apaga o clipe não-HD para economizar espaço
264
+ if os.path.exists(current_clip_path) and current_clip_path != hd_clip_path:
265
+ os.remove(current_clip_path)
266
+ current_clip_path = hd_clip_path
267
+ except Exception as e:
268
+ logger.error(f"Falha na masterização HD do clipe {i+1}: {e}. Usando versão padrão.")
269
+
270
+ # 4. Adicionar caminho do clipe final à lista
271
+ final_video_clip_paths.append(current_clip_path)
272
 
 
 
 
 
 
 
 
273
  #if use_refiner:
274
  # progress(0.8, desc="Refinando continuidade visual...")
275
  # # [REATORADO] Chamada para o novo especialista
276
+ # # OBS: Refinamento foi desativado conforme solicitado por degradar a lógica das keyframes.
277
+
278
+ # --- ETAPA 3: MONTAGEM FINAL ---
279
+ progress(0.98, desc="Montagem final dos clipes...")
280
+ final_video_path = os.path.join(self.workspace_dir, f"filme_final_{run_timestamp}.mp4")
281
+ self.concatenate_videos_ffmpeg(final_video_clip_paths, final_video_path)
282
 
283
+ # --- ETAPA 4: LIMPEZA ---
284
+ logger.info("Limpando arquivos temporários...")
285
+ try:
286
+ shutil.rmtree(temp_latent_dir)
287
+ shutil.rmtree(temp_video_clips_dir)
288
+ concat_list_path = os.path.join(self.workspace_dir, "concat_list.txt")
289
+ if os.path.exists(concat_list_path):
290
+ os.remove(concat_list_path)
291
+ except OSError as e:
292
+ logger.warning(f"Não foi possível remover os diretórios temporários: {e}")
293
+
294
+ logger.info(f"Processo concluído! Vídeo final salvo em: {final_video_path}")
295
+ yield {"final_path": final_video_path}
296
 
297
  def _generate_video_and_audio_from_latents(self, latent_tensor, audio_prompt, base_name):
298
+ # Este método agora opera em um diretório temporário para os clipes
299
+ temp_video_clips_dir = os.path.dirname(os.path.join(self.workspace_dir, base_name)) # Hack para obter o diretório correto
300
+ silent_video_path = os.path.join(temp_video_clips_dir, f"{base_name}_silent.mp4")
301
+
302
  pixel_tensor = self.latents_to_pixels(latent_tensor)
303
  self.save_video_from_tensor(pixel_tensor, silent_video_path, fps=24)
304
  del pixel_tensor; gc.collect(); torch.cuda.empty_cache()
 
309
  capture_output=True, text=True, check=True)
310
  frag_duration = float(result.stdout.strip())
311
  except (subprocess.CalledProcessError, ValueError, FileNotFoundError):
312
+ logger.warning(f"ffprobe falhou. Calculando duração manualmente a partir dos latentes.")
313
+ # O VAE interpola, então o número de frames é (num_latentes - 1) * 8 + 1 (aproximadamente)
314
+ num_pixel_frames = (latent_tensor.shape[2] - 1) * 8 + 1
315
  frag_duration = num_pixel_frames / 24.0
316
 
317
+ # Salva o vídeo com áudio no mesmo diretório temporário
318
  video_with_audio_path = audio_specialist_singleton.generate_audio_for_video(
319
  video_path=silent_video_path, prompt=audio_prompt,
320
+ duration_seconds=frag_duration,
321
+ output_path_override=os.path.join(temp_video_clips_dir, f"{base_name}_with_audio.mp4")
322
+ )
323
 
324
  if os.path.exists(silent_video_path):
325
  os.remove(silent_video_path)