EuuIia commited on
Commit
98350dd
·
verified ·
1 Parent(s): 97084c3

Update api/ltx_server.py

Browse files
Files changed (1) hide show
  1. api/ltx_server.py +56 -88
api/ltx_server.py CHANGED
@@ -438,107 +438,75 @@ class VideoService:
438
 
439
 
440
 
 
 
 
 
 
 
441
 
442
-
443
- def _gerar_lista_com_transicoes(self, pasta: str, video_paths: list[str], crossfade_frames: int = 8) -> list[str]:
444
- import os, subprocess, json
445
-
446
- def get_duracao_frames(video):
447
- """Retorna duração em frames (int)."""
448
- cmd = f'ffprobe -v error -select_streams v:0 -show_entries stream=nb_frames -of json "{video}"'
449
- try:
450
- res = subprocess.check_output(cmd, shell=True)
451
- info = json.loads(res)
452
- return int(info["streams"][0]["nb_frames"])
453
- except Exception:
454
- return 0
455
-
456
- def run_safe(cmd, desc):
457
- print(f"[DEBUG] {desc}: {cmd}")
458
- result = subprocess.run(cmd, shell=True)
459
- if result.returncode != 0:
460
- print(f"[WARN] Falha ao executar: {desc}")
461
- return result.returncode == 0
462
-
463
  nova_lista = []
464
- video_paths = [v for v in video_paths if os.path.isfile(v)]
465
- print(f"[DEBUG] Iniciando geração de transições ({len(video_paths)} vídeos, fade={crossfade_frames}f)")
466
 
467
- for i, video_atual in enumerate(video_paths):
468
- base_nome = os.path.splitext(os.path.basename(video_atual))[0]
469
- dur_frames = get_duracao_frames(video_atual)
470
- if dur_frames <= crossfade_frames * 2:
471
- print(f"[WARN] {video_atual} tem poucos frames ({dur_frames}). Pulando...")
472
- continue
473
 
474
- # 1️⃣ PRIMEIRO VÍDEO
475
- if i == 0:
476
- podado_fim = os.path.join(pasta, f"{base_nome}_fim.mp4")
477
- cmd = (
478
- f'ffmpeg -y -i "{video_atual}" -filter:v "trim=0:{dur_frames - crossfade_frames},setpts=PTS-STARTPTS" '
479
- f'-an "{podado_fim}"'
480
- )
481
- if run_safe(cmd, f"Podando fim de {video_atual}"):
482
- nova_lista.append(podado_fim)
483
 
484
- # 2️⃣ GERAR TRANSIÇÃO COM O PRÓXIMO
485
- if i < len(video_paths) - 1:
486
- prox = video_paths[i + 1]
487
- prox_nome = os.path.splitext(os.path.basename(prox))[0]
488
- dur_prox = get_duracao_frames(prox)
 
 
 
 
 
 
489
 
490
- # cortar fim do atual
491
- fim_tmp = os.path.join(pasta, f"{base_nome}_fimtmp.mp4")
492
- cmd_fim = (
493
- f'ffmpeg -y -ss {max((dur_frames - crossfade_frames) / 24, 0):.3f} -i "{video_atual}" '
494
- f'-frames:v {crossfade_frames} -an "{fim_tmp}"'
 
 
495
  )
496
- run_safe(cmd_fim, f"Extraindo fim de {base_nome}")
 
497
 
498
- # cortar início do próximo
499
- inicio_tmp = os.path.join(pasta, f"{prox_nome}_iniciotmp.mp4")
500
- cmd_inicio = (
501
- f'ffmpeg -y -i "{prox}" -frames:v {crossfade_frames} -an "{inicio_tmp}"'
 
 
 
 
502
  )
503
- run_safe(cmd_inicio, f"Extraindo início de {prox_nome}")
 
 
504
 
505
- # crossfade (vídeo sem áudio)
506
- transicao = os.path.join(pasta, f"transicao_{i+1}.mp4")
507
- cmd_xfade = (
508
- f'ffmpeg -y -i "{fim_tmp}" -i "{inicio_tmp}" '
509
- f'-filter_complex "xfade=transition=fade:duration={crossfade_frames/24:.3f}:offset=0" '
510
- f'-c:v libx264 -pix_fmt yuv420p -an "{transicao}"'
511
  )
512
- if run_safe(cmd_xfade, f"Transição {i+1}"):
513
- nova_lista.append(transicao)
514
-
515
- # podar início/fim do próximo
516
- if i + 1 < len(video_paths) - 1:
517
- podado_if = os.path.join(pasta, f"{prox_nome}_if.mp4")
518
- cmd_if = (
519
- f'ffmpeg -y -i "{prox}" '
520
- f'-filter:v "trim={crossfade_frames}:{dur_prox - crossfade_frames},setpts=PTS-STARTPTS" '
521
- f'-an "{podado_if}"'
522
- )
523
- if run_safe(cmd_if, f"Podando início/fim de {prox_nome}"):
524
- nova_lista.append(podado_if)
525
- else:
526
- podado_inicio = os.path.join(pasta, f"{prox_nome}_inicio.mp4")
527
- cmd_ini = (
528
- f'ffmpeg -y -i "{prox}" '
529
- f'-filter:v "trim={crossfade_frames}:{dur_prox},setpts=PTS-STARTPTS" -an "{podado_inicio}"'
530
- )
531
- if run_safe(cmd_ini, f"Podando início de {prox_nome}"):
532
- nova_lista.append(podado_inicio)
533
 
534
- # 🔹 Remover vídeos vazios
535
- nova_lista = [v for v in nova_lista if os.path.isfile(v) and get_duracao_frames(v) > 0]
536
- print(f"[DEBUG] Lista final para concatenação: {len(nova_lista)} vídeos válidos")
537
- for v in nova_lista:
538
- print(f" - {v}")
539
 
540
- return nova_list
541
-
542
  def _concat_mp4s_no_reencode(self, lista_mp4, output_path):
543
 
544
  with tempfile.NamedTemporaryFile("w", delete=False, suffix=".txt") as f:
 
438
 
439
 
440
 
441
+ def _gerar_lista_com_transicoes(self, pasta: str, video_paths: list[str], crossfade_frames: int = 8) -> list[str]:
442
+ """
443
+ Gera uma nova lista de vídeos aplicando transições suaves (blend frame a frame)
444
+ seguindo exatamente a lógica linear de Carlos.
445
+ """
446
+ import os, subprocess, shutil
447
 
448
+ poda = crossfade_frames
449
+ total_partes = len(video_paths)
450
+ video_anterior_fade_fim = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
451
  nova_lista = []
 
 
452
 
453
+ print(f"[DEBUG] Iniciando pipeline com {total_partes} vídeos e {poda} frames de crossfade")
 
 
 
 
 
454
 
455
+ for i in range(total_partes):
456
+ base = os.path.splitext(os.path.basename(video_paths[i]))[0]
457
+ video_clone = os.path.join(pasta, f"{base}_clone_{i}.mp4")
458
+ shutil.copy(video_paths[i], video_clone)
 
 
 
 
 
459
 
460
+ # --- PODA ---
461
+ video_podado = os.path.join(pasta, f"{base}_podado_{i}.mp4")
462
+ trim_ini = poda if i > 0 else 0
463
+ trim_fim = -poda if i < total_partes - 1 else 0
464
+ filtro_trim = f"trim=start_frame={trim_ini}"
465
+ if trim_fim != 0:
466
+ filtro_trim += f":end_frame=nb_frames+{trim_fim}"
467
+ cmd_poda = f'ffmpeg -y -hide_banner -loglevel error -i "{video_clone}" -vf "{filtro_trim},setpts=PTS-STARTPTS" -an "{video_podado}"'
468
+ subprocess.run(cmd_poda, shell=True, check=True)
469
+ nova_lista.append(video_podado)
470
+ print(f"[DEBUG] [{i}] Podado -> {video_podado}")
471
 
472
+ # --- FADE_INI ---
473
+ video_fade_ini = None
474
+ if i > 0:
475
+ video_fade_ini = os.path.join(pasta, f"{base}_fade_ini_{i}.mp4")
476
+ cmd_ini = (
477
+ f'ffmpeg -y -hide_banner -loglevel error -i "{video_clone}" '
478
+ f'-vf "trim=end_frame={poda},setpts=PTS-STARTPTS" -an "{video_fade_ini}"'
479
  )
480
+ subprocess.run(cmd_ini, shell=True, check=True)
481
+ print(f"[DEBUG] Fade_ini ({poda} frames) -> {video_fade_ini}")
482
 
483
+ # --- TRANSIÇÃO ---
484
+ if video_anterior_fade_fim and video_fade_ini:
485
+ transicao = os.path.join(pasta, f"transicao_{i-1}_{i}.mp4")
486
+ cmd_blend = (
487
+ f'ffmpeg -y -hide_banner -loglevel error '
488
+ f'-i "{video_anterior_fade_fim}" -i "{video_fade_ini}" '
489
+ f'-filter_complex "[0:v][1:v]blend=all_expr=\'A*(1-T/{poda})+B*(T/{poda})\',format=yuv420p" '
490
+ f'-frames:v {poda} "{transicao}"'
491
  )
492
+ subprocess.run(cmd_blend, shell=True, check=True)
493
+ nova_lista.append(transicao)
494
+ print(f"[DEBUG] Transição criada -> {transicao}")
495
 
496
+ # --- FADE_FIM ---
497
+ if i < total_partes - 1:
498
+ video_fade_fim = os.path.join(pasta, f"{base}_fade_fim_{i}.mp4")
499
+ cmd_fim = (
500
+ f'ffmpeg -y -hide_banner -loglevel error -i "{video_clone}" '
501
+ f'-vf "trim=start_frame=nb_frames-{poda},setpts=PTS-STARTPTS" -an "{video_fade_fim}"'
502
  )
503
+ subprocess.run(cmd_fim, shell=True, check=True)
504
+ video_anterior_fade_fim = video_fade_fim
505
+ print(f"[DEBUG] Fade_fim preparado -> {video_fade_fim}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
506
 
507
+ print(f"[DEBUG] Nova lista finalizada com {len(nova_lista)} partes.")
508
+ return nova_lista
 
 
 
509
 
 
 
510
  def _concat_mp4s_no_reencode(self, lista_mp4, output_path):
511
 
512
  with tempfile.NamedTemporaryFile("w", delete=False, suffix=".txt") as f: