EuuIia commited on
Commit
4e44c56
·
verified ·
1 Parent(s): 8826e77

Update api/ltx_server.py

Browse files
Files changed (1) hide show
  1. api/ltx_server.py +75 -111
api/ltx_server.py CHANGED
@@ -436,125 +436,92 @@ class VideoService:
436
  return chunks
437
 
438
 
439
- def _concat_crossfade_cascade(self, video_paths: List[str], crossfade_frames: int = 8, output_path: str = "/tmp/final_output.mp4"):
 
440
  """
441
- Concatena uma lista de vídeos aplicando crossfade de 'crossfade_frames' entre cada par.
 
 
442
  Args:
443
- video_paths: lista de caminhos dos vídeos a concatenar
444
- crossfade_frames: quantidade de frames para o crossfade
445
- output_path: caminho do vídeo final gerado
 
446
  """
447
- if not video_paths or len(video_paths) == 1:
448
- # Apenas copiar se houver 1 ou nenhum vídeo
449
- if video_paths:
450
- subprocess.run(f"cp '{video_paths[0]}' '{output_path}'", shell=True, check=True)
451
- return output_path
452
-
453
- # Lista temporária de vídeos intermediários
454
- temp_videos = []
455
 
456
- # Começamos com o primeiro vídeo
457
- prev_video = video_paths[0]
 
458
 
459
- for i in range(1, len(video_paths)):
460
- next_video = video_paths[i]
461
-
462
- # Cria arquivo temporário para resultado do crossfade
463
- temp_out = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
464
- temp_out_path = temp_out.name
465
- temp_out.close()
466
-
467
- # Comando FFmpeg
468
- cmd = f"""
469
- ffmpeg -y -i "{prev_video}" -i "{next_video}" -filter_complex "
470
- [0:v]trim=0:-{crossfade_frames},setpts=PTS-STARTPTS[v_prev_main];
471
- [0:v]trim=-{crossfade_frames},setpts=PTS-STARTPTS[v_prev_fade];
472
- [1:v]trim=0:{crossfade_frames},setpts=PTS-STARTPTS[v_next_fade];
473
- [1:v]trim={crossfade_frames}:,setpts=PTS-STARTPTS[v_next_main];
474
- [v_prev_fade][v_next_fade]blend=all_expr='A*(1-T/{crossfade_frames})+B*(T/{crossfade_frames})'[crossfade];
475
- [v_prev_main][crossfade][v_next_main]concat=n=3:v=1:a=0[v]
476
- " -map "[v]" -c:v libx264 -pix_fmt yuv420p "{temp_out_path}"
477
- """
478
 
479
- # Executa FFmpeg
480
- subprocess.run(cmd, shell=True, check=True)
481
 
482
- # Atualiza prev_video para o próximo crossfade
483
- prev_video = temp_out_path
484
- temp_videos.append(temp_out_path)
 
 
 
485
 
486
- # Move o último vídeo processado para o destino final
487
- os.rename(prev_video, output_path)
 
 
488
 
489
- # Remove vídeos temporários antigos
490
- for tmp in temp_videos[:-1]:
491
- if os.path.exists(tmp):
492
- os.remove(tmp)
 
 
 
 
 
493
 
494
- return output_path
495
 
496
-
497
- def _concat_crossfade_cascade1(self, video_paths: List[str], output_path: str, crossfade_frames: int = 8):
498
  """
499
- Concatena vídeos em cascata com crossfade, usando FFmpeg.
500
-
501
- Args:
502
- video_paths (List[str]): Lista de caminhos dos vídeos a serem concatenados.
503
- output_path (str): Caminho do arquivo final de saída.
504
- crossfade_frames (int): Número de frames para o crossfade.
505
  """
506
- if len(video_paths) < 2:
507
- raise ValueError("É necessário pelo menos 2 vídeos para concatenação com crossfade.")
508
-
509
- # Cria uma lista de temporários
510
- tmp_videos = []
511
- for i in range(len(video_paths) - 1):
512
- # Nome do arquivo temporário
513
- tmp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4").name
514
- tmp_videos.append(tmp_file)
515
-
516
- # Define vídeos para crossfade
517
- if i == 0:
518
- vid1 = video_paths[0]
519
- else:
520
- vid1 = tmp_videos[i-1]
521
- vid2 = video_paths[i+1]
522
-
523
- # Filter complex FFmpeg
524
- filter_complex = f"""
525
- [0:v]trim=0:-{crossfade_frames},setpts=PTS-STARTPTS[v0pre];
526
- [0:v]trim=-{crossfade_frames},setpts=PTS-STARTPTS[v0fade];
527
- [1:v]trim=0:{crossfade_frames},setpts=PTS-STARTPTS[v1fade];
528
- [v0fade][v1fade]blend=all_expr='A*(1-T/{crossfade_frames})+B*(T/{crossfade_frames})'[xf];
529
- [1:v]trim={crossfade_frames}:,setpts=PTS-STARTPTS[v1post];
530
- [v0pre][xf][v1post]concat=n=3:v=1:a=0[v]
531
- """
532
-
533
- cmd = [
534
- "ffmpeg", "-y",
535
- "-i", vid1,
536
- "-i", vid2,
537
- "-filter_complex", filter_complex,
538
- "-map", "[v]",
539
- "-c:v", "libx264",
540
- "-pix_fmt", "yuv420p",
541
- tmp_file
542
- ]
543
-
544
- print(f"[DEBUG] Executando: {' '.join(cmd)}")
545
- subprocess.run(cmd, check=True)
546
-
547
- # Último temporário é o arquivo final
548
- final_tmp = tmp_videos[-1]
549
- os.rename(final_tmp, output_path)
550
-
551
- # Limpa os temporários intermediários
552
- for tmp in tmp_videos[:-1]:
553
- if os.path.exists(tmp):
554
- os.remove(tmp)
555
-
556
- print(f"[INFO] Vídeo final gerado: {output_path}")
557
 
 
 
558
 
559
  def generate(
560
  self,
@@ -784,12 +751,9 @@ class VideoService:
784
  print(f"[DEBUG] Falha no move; usando tmp como final: {e}")
785
 
786
  final_concat = os.path.join(results_dir, f"concat_fim_{used_seed}.mp4")
787
- #self._concat_mp4s_no_reencode(partes_mp4, final_concat)
788
- self._concat_crossfade_cascade(
789
- video_paths=partes_mp4,
790
- crossfade_frames=8,
791
- output_path=final_concat,
792
- )
793
 
794
 
795
  self._log_gpu_memory("Fim da Geração")
 
436
  return chunks
437
 
438
 
439
+
440
+ def _gerar_lista_com_transicoes(self, video_paths: List[str], crossfade_frames: int = 8) -> List[str]:
441
  """
442
+ Gera uma nova lista de vídeos com cortes e transições de crossfade.
443
+ Cada transição é de 'crossfade_frames' frames.
444
+
445
  Args:
446
+ video_paths: lista de caminhos de vídeos originais
447
+ crossfade_frames: quantidade de frames para transição
448
+ Returns:
449
+ List[str]: nova lista de caminhos de vídeos, incluindo transições
450
  """
451
+ nova_lista = []
 
 
 
 
 
 
 
452
 
453
+ for i in range(len(video_paths)):
454
+ video_atual = video_paths[i]
455
+ video_proximo = video_paths[i + 1] if i + 1 < len(video_paths) else None
456
 
457
+ # ---- 1. Video atual podado ----
458
+ if i == 0:
459
+ # Primeiro vídeo: remove últimos crossfade_frames
460
+ podado = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4").name
461
+ cmd_trim = f'ffmpeg -y -i "{video_atual}" -vf "trim=0:-{crossfade_frames},setpts=PTS-STARTPTS" "{podado}"'
462
+ elif video_proximo:
463
+ # Vídeos do meio: remove primeiros e últimos crossfade_frames
464
+ podado = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4").name
465
+ cmd_trim = f'ffmpeg -y -i "{video_atual}" -vf "trim={crossfade_frames}:-{crossfade_frames},setpts=PTS-STARTPTS" "{podado}"'
466
+ else:
467
+ # Último vídeo: remove primeiros crossfade_frames
468
+ podado = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4").name
469
+ cmd_trim = f'ffmpeg -y -i "{video_atual}" -vf "trim={crossfade_frames}:,setpts=PTS-STARTPTS" "{podado}"'
 
 
 
 
 
 
470
 
471
+ subprocess.run(cmd_trim, shell=True, check=True)
472
+ nova_lista.append(podado)
473
 
474
+ # ---- 2. Gerar transição, se houver próximo vídeo ----
475
+ if video_proximo:
476
+ # Extrair últimos frames do atual
477
+ temp_fim = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4").name
478
+ cmd_fim = f'ffmpeg -y -i "{video_atual}" -vf "trim=-{crossfade_frames},setpts=PTS-STARTPTS" "{temp_fim}"'
479
+ subprocess.run(cmd_fim, shell=True, check=True)
480
 
481
+ # Extrair primeiros frames do próximo
482
+ temp_inicio = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4").name
483
+ cmd_inicio = f'ffmpeg -y -i "{video_proximo}" -vf "trim=0:{crossfade_frames},setpts=PTS-STARTPTS" "{temp_inicio}"'
484
+ subprocess.run(cmd_inicio, shell=True, check=True)
485
 
486
+ # Criar vídeo de transição
487
+ transicao = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4").name
488
+ cmd_blend = f"""
489
+ ffmpeg -y -i "{temp_fim}" -i "{temp_inicio}" -filter_complex "
490
+ [0:v][1:v]blend=all_expr='A*(1-T/{crossfade_frames})+B*(T/{crossfade_frames})'[v]
491
+ " -map "[v]" -c:v libx264 -pix_fmt yuv420p "{transicao}"
492
+ """
493
+ subprocess.run(cmd_blend, shell=True, check=True)
494
+ nova_lista.append(transicao)
495
 
496
+ return nova_lista
497
 
498
+ def _concat_mp4s_no_reencode(self, mp4_list: List[str], out_path: str):
 
499
  """
500
+ Concatena múltiplos MP4s sem reencode usando o demuxer do ffmpeg.
501
+ ATENÇÃO: todos os arquivos precisam ter mesmo codec, fps, resolução etc.
 
 
 
 
502
  """
503
+ if not mp4_list or len(mp4_list) < 2:
504
+ raise ValueError("Forneça pelo menos dois arquivos MP4 para concatenar.")
505
+
506
+ # Cria lista temporária para o ffmpeg
507
+ with tempfile.NamedTemporaryFile("w", delete=False, suffix=".txt") as f:
508
+ for mp4 in mp4_list:
509
+ f.write(f"file '{os.path.abspath(mp4)}'\n")
510
+ list_path = f.name
511
+
512
+ cmd = f"ffmpeg -y -f concat -safe 0 -i {list_path} -c copy {out_path}"
513
+ print(f"[DEBUG] Concat: {cmd}")
514
+
515
+ try:
516
+ subprocess.check_call(shlex.split(cmd))
517
+ finally:
518
+ try:
519
+ os.remove(list_path)
520
+ except Exception:
521
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
522
 
523
+
524
+
525
 
526
  def generate(
527
  self,
 
751
  print(f"[DEBUG] Falha no move; usando tmp como final: {e}")
752
 
753
  final_concat = os.path.join(results_dir, f"concat_fim_{used_seed}.mp4")
754
+ final_concat_new = self._gerar_lista_com_transicoes(video_paths=final_concat, crossfade_frames=8)
755
+ self._concat_mp4s_no_reencode(partes_mp4, final_concat_new)
756
+
 
 
 
757
 
758
 
759
  self._log_gpu_memory("Fim da Geração")