Update api/ltx_server.py
Browse files- api/ltx_server.py +98 -10
api/ltx_server.py
CHANGED
|
@@ -108,6 +108,68 @@ def _query_gpu_processes_via_nvidiasmi(device_index: int) -> List[Dict]:
|
|
| 108 |
continue
|
| 109 |
return results
|
| 110 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
def _gpu_process_table(processes: List[Dict], current_pid: int) -> str:
|
| 112 |
if not processes:
|
| 113 |
return " - Processos ativos: (nenhum)\n"
|
|
@@ -632,8 +694,8 @@ class VideoService:
|
|
| 632 |
target_frames_rounded = round(duration * FPS)
|
| 633 |
n_val = round((float(target_frames_rounded) - 1.0) / 8.0)
|
| 634 |
actual_num_frames = max(9, min(MAX_NUM_FRAMES, int(n_val * 8 + 1)))
|
| 635 |
-
height_padded = ((height - 1) //
|
| 636 |
-
width_padded = ((width - 1) //
|
| 637 |
padding_values = calculate_padding(height, width, height_padded, width_padded)
|
| 638 |
generator = torch.Generator(device=self.device).manual_seed(used_seed)
|
| 639 |
|
|
@@ -652,14 +714,29 @@ class VideoService:
|
|
| 652 |
print(f"[DEBUG] Conditioning items: {len(conditioning_items)}")
|
| 653 |
|
| 654 |
call_kwargs = {
|
| 655 |
-
"prompt": prompt,
|
| 656 |
-
"
|
| 657 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 658 |
"conditioning_items": conditioning_items if conditioning_items else None,
|
| 659 |
-
"media_items": None,
|
| 660 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 661 |
}
|
| 662 |
-
|
|
|
|
| 663 |
latents = None
|
| 664 |
|
| 665 |
try:
|
|
@@ -675,9 +752,20 @@ class VideoService:
|
|
| 675 |
|
| 676 |
first_pass_config = self.config.get("first_pass", {}).copy()
|
| 677 |
downscale_factor = self.config.get("downscale_factor", 0.666)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 678 |
|
| 679 |
-
|
| 680 |
-
downscaled_height = int(height_padded * downscale_factor)
|
| 681 |
|
| 682 |
first_pass_kwargs = call_kwargs.copy()
|
| 683 |
first_pass_kwargs.update({
|
|
|
|
| 108 |
continue
|
| 109 |
return results
|
| 110 |
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def calculate_new_dimensions(orig_w, orig_h, divisor=8):
|
| 114 |
+
"""
|
| 115 |
+
Calcula novas dimensões mantendo a proporção, garantindo que ambos os
|
| 116 |
+
lados sejam divisíveis pelo divisor especificado (padrão 8).
|
| 117 |
+
"""
|
| 118 |
+
if orig_w == 0 or orig_h == 0:
|
| 119 |
+
# Retorna um valor padrão seguro
|
| 120 |
+
return 512, 512
|
| 121 |
+
|
| 122 |
+
# Preserva a orientação (paisagem vs. retrato)
|
| 123 |
+
if orig_w >= orig_h:
|
| 124 |
+
# Paisagem ou quadrado
|
| 125 |
+
aspect_ratio = orig_w / orig_h
|
| 126 |
+
# Começa com uma altura base e calcula a largura
|
| 127 |
+
new_h = 512 # Altura base para paisagem
|
| 128 |
+
new_w = new_h * aspect_ratio
|
| 129 |
+
else:
|
| 130 |
+
# Retrato
|
| 131 |
+
aspect_ratio = orig_h / orig_w
|
| 132 |
+
# Começa com uma largura base e calcula a altura
|
| 133 |
+
new_w = 512 # Largura base para retrato
|
| 134 |
+
new_h = new_w * aspect_ratio
|
| 135 |
+
|
| 136 |
+
# Arredonda AMBOS os valores para o múltiplo mais próximo do divisor
|
| 137 |
+
final_w = int(round(new_w / divisor)) * divisor
|
| 138 |
+
final_h = int(round(new_h / divisor)) * divisor
|
| 139 |
+
|
| 140 |
+
# Garante que as dimensões não sejam zero após o arredondamento
|
| 141 |
+
final_w = max(divisor, final_w)
|
| 142 |
+
final_h = max(divisor, final_h)
|
| 143 |
+
|
| 144 |
+
print(f"[Dimension Calc] Original: {orig_w}x{orig_h} -> Calculado: {new_w:.0f}x{new_h:.0f} -> Final (divisível por {divisor}): {final_w}x{final_h}")
|
| 145 |
+
return final_h, final_w # Retorna (altura, largura)
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def handle_media_upload_for_dims(filepath, current_h, current_w):
|
| 149 |
+
"""
|
| 150 |
+
Esta função agora usará o novo cálculo robusto.
|
| 151 |
+
(O corpo desta função não precisa de alterações, pois ela já chama a função de cálculo)
|
| 152 |
+
"""
|
| 153 |
+
if not filepath or not os.path.exists(str(filepath)):
|
| 154 |
+
return gr.update(value=current_h), gr.update(value=current_w)
|
| 155 |
+
try:
|
| 156 |
+
if str(filepath).lower().endswith(('.png', '.jpg', '.jpeg', '.webp')):
|
| 157 |
+
with Image.open(filepath) as img:
|
| 158 |
+
orig_w, orig_h = img.size
|
| 159 |
+
else: # Assumir que é um vídeo
|
| 160 |
+
with imageio.get_reader(filepath) as reader:
|
| 161 |
+
meta = reader.get_meta_data()
|
| 162 |
+
orig_w, orig_h = meta.get('size', (current_w, current_h))
|
| 163 |
+
|
| 164 |
+
# Chama a nova função corrigida
|
| 165 |
+
new_h, new_w = calculate_new_dimensions(orig_w, orig_h)
|
| 166 |
+
|
| 167 |
+
return gr.update(value=new_h), gr.update(value=new_w)
|
| 168 |
+
except Exception as e:
|
| 169 |
+
print(f"Erro ao processar mídia para dimensões: {e}")
|
| 170 |
+
return gr.update(value=current_h), gr.update(value=current_w)
|
| 171 |
+
|
| 172 |
+
|
| 173 |
def _gpu_process_table(processes: List[Dict], current_pid: int) -> str:
|
| 174 |
if not processes:
|
| 175 |
return " - Processos ativos: (nenhum)\n"
|
|
|
|
| 694 |
target_frames_rounded = round(duration * FPS)
|
| 695 |
n_val = round((float(target_frames_rounded) - 1.0) / 8.0)
|
| 696 |
actual_num_frames = max(9, min(MAX_NUM_FRAMES, int(n_val * 8 + 1)))
|
| 697 |
+
height_padded = ((height - 1) // 8 + 1) * 8
|
| 698 |
+
width_padded = ((width - 1) // 8 + 1) * 8
|
| 699 |
padding_values = calculate_padding(height, width, height_padded, width_padded)
|
| 700 |
generator = torch.Generator(device=self.device).manual_seed(used_seed)
|
| 701 |
|
|
|
|
| 714 |
print(f"[DEBUG] Conditioning items: {len(conditioning_items)}")
|
| 715 |
|
| 716 |
call_kwargs = {
|
| 717 |
+
"prompt": prompt,
|
| 718 |
+
"negative_prompt": negative_prompt,
|
| 719 |
+
"height": height_padded,
|
| 720 |
+
"width": width_padded,
|
| 721 |
+
"num_frames": actual_num_frames,
|
| 722 |
+
"frame_rate": int(FPS),
|
| 723 |
+
"generator": generator,
|
| 724 |
+
"output_type": "latent",
|
| 725 |
"conditioning_items": conditioning_items if conditioning_items else None,
|
| 726 |
+
"media_items": None,
|
| 727 |
+
"decode_timestep": self.config["decode_timestep"],
|
| 728 |
+
"decode_noise_scale": self.config["decode_noise_scale"],
|
| 729 |
+
"stochastic_sampling": self.config["stochastic_sampling"],
|
| 730 |
+
"image_cond_noise_scale": 0.01,
|
| 731 |
+
"is_video": True,
|
| 732 |
+
"vae_per_channel_normalize": True,
|
| 733 |
+
"mixed_precision": (self.config["precision"] == "mixed_precision"),
|
| 734 |
+
"offload_to_cpu": False,
|
| 735 |
+
"enhance_prompt": False,
|
| 736 |
+
"skip_layer_strategy": SkipLayerStrategy.AttentionValues,
|
| 737 |
}
|
| 738 |
+
print(f"[DEBUG] output_type={call_kwargs['output_type']} skip_layer_strategy={call_kwargs['skip_layer_strategy']}")
|
| 739 |
+
|
| 740 |
latents = None
|
| 741 |
|
| 742 |
try:
|
|
|
|
| 752 |
|
| 753 |
first_pass_config = self.config.get("first_pass", {}).copy()
|
| 754 |
downscale_factor = self.config.get("downscale_factor", 0.666)
|
| 755 |
+
|
| 756 |
+
unrounded_width = width_padded * downscale_factor
|
| 757 |
+
unrounded_height = height_padded * downscale_factor
|
| 758 |
+
|
| 759 |
+
# Sanitiza as dimensões para serem divisíveis pelo fator do VAE (geralmente 8)
|
| 760 |
+
# Usamos o mesmo divisor da UI para consistência.
|
| 761 |
+
divisor = 8
|
| 762 |
+
downscaled_width = int(round(unrounded_width / divisor)) * divisor
|
| 763 |
+
downscaled_height = int(round(unrounded_height / divisor)) * divisor
|
| 764 |
+
|
| 765 |
+
downscaled_width = max(divisor, downscaled_width)
|
| 766 |
+
downscaled_height = max(divisor, downscaled_height)
|
| 767 |
|
| 768 |
+
print(f"[DEBUG] Dimensões do First Pass: Calculado ({unrounded_width:.0f}x{unrounded_height:.0f}) -> Sanitizado ({downscaled_width}x{downscaled_height})")
|
|
|
|
| 769 |
|
| 770 |
first_pass_kwargs = call_kwargs.copy()
|
| 771 |
first_pass_kwargs.update({
|