euiia commited on
Commit
97d3157
·
verified ·
1 Parent(s): be881e5

Upload audio_specialist.py

Browse files
Files changed (1) hide show
  1. managers/audio_specialist.py +163 -0
managers/audio_specialist.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # audio_specialist.py
2
+ # Especialista ADUC para geração de áudio, com gerenciamento de memória GPU.
3
+ # Copyright (C) 4 de Agosto de 2025 Carlos Rodrigues dos Santos
4
+
5
+ import torch
6
+ import logging
7
+ import subprocess
8
+ import os
9
+ import time
10
+ import yaml
11
+ import gc
12
+ from pathlib import Path
13
+ import gradio as gr
14
+
15
+ # Importa as classes e funções necessárias do MMAudio
16
+ try:
17
+ from mmaudio.eval_utils import ModelConfig, all_model_cfg, generate as mmaudio_generate, load_video, make_video
18
+ from mmaudio.model.flow_matching import FlowMatching
19
+ from mmaudio.model.networks import MMAudio, get_my_mmaudio
20
+ from mmaudio.model.utils.features_utils import FeaturesUtils
21
+ from mmaudio.model.sequence_config import SequenceConfig
22
+ except ImportError:
23
+ raise ImportError("MMAudio não foi encontrado. Por favor, instale-o a partir do GitHub: git+https://github.com/hkchengrex/MMAudio.git")
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ class AudioSpecialist:
28
+ """
29
+ Especialista responsável por gerar áudio para fragmentos de vídeo.
30
+ Gerencia o carregamento e descarregamento de modelos de áudio da VRAM.
31
+ """
32
+ def __init__(self, workspace_dir):
33
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
34
+ self.cpu_device = torch.device("cpu")
35
+ self.dtype = torch.bfloat16 if self.device == "cuda" else torch.float32
36
+ self.workspace_dir = workspace_dir
37
+
38
+ self.model_config: ModelConfig = all_model_cfg['large_44k_v2']
39
+ self.net: MMAudio = None
40
+ self.feature_utils: FeaturesUtils = None
41
+ self.seq_cfg: SequenceConfig = None
42
+
43
+ self._load_models_to_cpu()
44
+
45
+ def _load_models_to_cpu(self):
46
+ """Carrega os modelos MMAudio para a memória da CPU na inicialização."""
47
+ try:
48
+ logger.info("Verificando e baixando modelos MMAudio, se necessário...")
49
+ self.model_config.download_if_needed()
50
+
51
+ self.seq_cfg = self.model_config.seq_cfg
52
+
53
+ logger.info(f"Carregando modelo MMAudio: {self.model_config.model_name} para a CPU...")
54
+ self.net = get_my_mmaudio(self.model_config.model_name).eval()
55
+ self.net.load_weights(torch.load(self.model_config.model_path, map_location=self.cpu_device, weights_only=True))
56
+
57
+ logger.info("Carregando utilitários de features do MMAudio para a CPU...")
58
+ self.feature_utils = FeaturesUtils(
59
+ tod_vae_ckpt=self.model_config.vae_path,
60
+ synchformer_ckpt=self.model_config.synchformer_ckpt,
61
+ enable_conditions=True,
62
+ mode=self.model_config.mode,
63
+ bigvgan_vocoder_ckpt=self.model_config.bigvgan_16k_path,
64
+ need_vae_encoder=False
65
+ )
66
+ self.feature_utils = self.feature_utils.eval()
67
+ self.net.to(self.cpu_device)
68
+ self.feature_utils.to(self.cpu_device)
69
+ logger.info("Especialista de áudio pronto na CPU.")
70
+ except Exception as e:
71
+ logger.error(f"Falha ao carregar modelos de áudio: {e}", exc_info=True)
72
+ self.net = None
73
+
74
+ def to_gpu(self):
75
+ """Move os modelos e utilitários para a GPU antes da inferência."""
76
+ if self.device == 'cpu': return
77
+ logger.info(f"Movendo especialista de áudio para a GPU ({self.device})...")
78
+ self.net.to(self.device, self.dtype)
79
+ self.feature_utils.to(self.device, self.dtype)
80
+
81
+ def to_cpu(self):
82
+ """Move os modelos de volta para a CPU e limpa a VRAM após a inferência."""
83
+ if self.device == 'cpu': return
84
+ logger.info("Descarregando especialista de áudio da GPU...")
85
+ self.net.to(self.cpu_device)
86
+ self.feature_utils.to(self.cpu_device)
87
+ gc.collect()
88
+ if torch.cuda.is_available(): torch.cuda.empty_cache()
89
+
90
+ def generate_audio_for_video(self, video_path: str, prompt: str, duration_seconds: float, output_path_override: str = None) -> str:
91
+ """
92
+ Gera áudio para um arquivo de vídeo, aplicando um prompt negativo para evitar fala.
93
+
94
+ Args:
95
+ video_path (str): Caminho para o vídeo silencioso.
96
+ prompt (str): Descrição da cena para guiar a geração de SFX.
97
+ duration_seconds (float): Duração do áudio a ser gerado.
98
+
99
+ Returns:
100
+ str: Caminho para o novo arquivo de vídeo com áudio.
101
+ """
102
+ if self.net is None:
103
+ raise gr.Error("Modelo MMAudio não está carregado. Não é possível gerar áudio.")
104
+
105
+ logger.info("------------------------------------------------------")
106
+ logger.info("--- Gerando Áudio para Fragmento de Vídeo ---")
107
+ logger.info(f"--- Vídeo Fragmento: {os.path.basename(video_path)}")
108
+ logger.info(f"--- Duração: {duration_seconds:.2f}s")
109
+ logger.info(f"--- Prompt (Descrição da Cena): '{prompt}'")
110
+
111
+ negative_prompt = "human voice"
112
+ logger.info(f"--- Negative Prompt: '{negative_prompt}'")
113
+
114
+ if duration_seconds < 1:
115
+ logger.warning("Fragmento muito curto (<1s). Retornando vídeo silencioso.")
116
+ logger.info("------------------------------------------------------")
117
+ return video_path
118
+
119
+ if self.device == 'cpu':
120
+ logger.warning("Gerando áudio na CPU. Isso pode ser muito lento.")
121
+
122
+ try:
123
+ self.to_gpu()
124
+ with torch.no_grad():
125
+ rng = torch.Generator(device=self.device).manual_seed(int(time.time()))
126
+ fm = FlowMatching(min_sigma=0, inference_mode='euler', num_steps=25)
127
+
128
+ video_info = load_video(Path(video_path), duration_seconds)
129
+ self.seq_cfg.duration = video_info.duration_sec
130
+ self.net.update_seq_lengths(self.seq_cfg.latent_seq_len, self.seq_cfg.clip_seq_len, self.seq_cfg.sync_seq_len)
131
+
132
+ audios = mmaudio_generate(
133
+ clip_video=video_info.clip_frames.unsqueeze(0),
134
+ sync_video=video_info.sync_frames.unsqueeze(0),
135
+ text=[prompt],
136
+ negative_text=[negative_prompt],
137
+ feature_utils=self.feature_utils,
138
+ net=self.net,
139
+ fm=fm,
140
+ rng=rng,
141
+ cfg_strength=4.5
142
+ )
143
+ audio_waveform = audios.float().cpu()[0]
144
+
145
+ fragment_name = Path(video_path).stem
146
+ output_video_path = output_path_override if output_path_override else os.path.join(self.workspace_dir, f"{fragment_name}_com_audio.mp4")
147
+
148
+ make_video(video_info, Path(output_video_path), audio_waveform, sampling_rate=self.seq_cfg.sampling_rate)
149
+ logger.info(f"--- Fragmento com áudio salvo em: {os.path.basename(output_video_path)}")
150
+ logger.info("------------------------------------------------------")
151
+ return output_video_path
152
+ finally:
153
+ self.to_cpu()
154
+
155
+ # Singleton instantiation
156
+ try:
157
+ with open("config.yaml", 'r') as f:
158
+ config = yaml.safe_load(f)
159
+ WORKSPACE_DIR = config['application']['workspace_dir']
160
+ audio_specialist_singleton = AudioSpecialist(workspace_dir=WORKSPACE_DIR)
161
+ except Exception as e:
162
+ logger.error(f"Não foi possível inicializar o AudioSpecialist: {e}", exc_info=True)
163
+ audio_specialist_singleton = None