Aduc-sdr-cinematic-video / audio_specialist.py
EU-IA's picture
Update audio_specialist.py
452a3b8 verified
raw
history blame
7.4 kB
# audio_specialist.py
# Especialista ADUC para geração de áudio, com gerenciamento de memória GPU.
# Copyright (C) 4 de Agosto de 2025 Carlos Rodrigues dos Santos
import torch
import logging
import subprocess
import os
import time
import yaml
import gc
from pathlib import Path
import gradio as gr
# Importa as classes e funções necessárias do MMAudio
try:
from mmaudio.eval_utils import ModelConfig, all_model_cfg, generate as mmaudio_generate, load_video, make_video
from mmaudio.model.flow_matching import FlowMatching
from mmaudio.model.networks import MMAudio, get_my_mmaudio
from mmaudio.model.utils.features_utils import FeaturesUtils
from mmaudio.model.sequence_config import SequenceConfig
except ImportError:
raise ImportError("MMAudio não foi encontrado. Por favor, instale-o a partir do GitHub: git+https://github.com/hkchengrex/MMAudio.git")
logger = logging.getLogger(__name__)
class AudioSpecialist:
"""
Especialista responsável por gerar áudio para fragmentos de vídeo.
Gerencia o carregamento e descarregamento de modelos de áudio da VRAM.
"""
def __init__(self, workspace_dir):
self.device = "cuda" if torch.cuda.is_available() else "cpu"
self.cpu_device = torch.device("cpu")
self.dtype = torch.bfloat16 if self.device == "cuda" else torch.float32
self.workspace_dir = workspace_dir
self.model_config: ModelConfig = all_model_cfg['large_44k_v2']
self.net: MMAudio = None
self.feature_utils: FeaturesUtils = None
self.seq_cfg: SequenceConfig = None
self._load_models_to_cpu()
def _load_models_to_cpu(self):
"""Carrega os modelos MMAudio para a memória da CPU na inicialização."""
try:
logger.info("Verificando e baixando modelos MMAudio, se necessário...")
self.model_config.download_if_needed()
self.seq_cfg = self.model_config.seq_cfg
logger.info(f"Carregando modelo MMAudio: {self.model_config.model_name} para a CPU...")
self.net = get_my_mmaudio(self.model_config.model_name).eval()
self.net.load_weights(torch.load(self.model_config.model_path, map_location=self.cpu_device, weights_only=True))
logger.info("Carregando utilitários de features do MMAudio para a CPU...")
self.feature_utils = FeaturesUtils(
tod_vae_ckpt=self.model_config.vae_path,
synchformer_ckpt=self.model_config.synchformer_ckpt,
enable_conditions=True,
mode=self.model_config.mode,
bigvgan_vocoder_ckpt=self.model_config.bigvgan_16k_path,
need_vae_encoder=False
)
self.feature_utils = self.feature_utils.eval()
self.net.to(self.cpu_device)
self.feature_utils.to(self.cpu_device)
logger.info("Especialista de áudio pronto na CPU.")
except Exception as e:
logger.error(f"Falha ao carregar modelos de áudio: {e}", exc_info=True)
self.net = None
def to_gpu(self):
"""Move os modelos e utilitários para a GPU antes da inferência."""
if self.device == 'cpu': return
logger.info(f"Movendo especialista de áudio para a GPU ({self.device})...")
self.net.to(self.device, self.dtype)
self.feature_utils.to(self.device, self.dtype)
def to_cpu(self):
"""Move os modelos de volta para a CPU e limpa a VRAM após a inferência."""
if self.device == 'cpu': return
logger.info("Descarregando especialista de áudio da GPU...")
self.net.to(self.cpu_device)
self.feature_utils.to(self.cpu_device)
gc.collect()
if torch.cuda.is_available(): torch.cuda.empty_cache()
def generate_audio_for_video(self, video_path: str, prompt: str, duration_seconds: float) -> str:
"""
Gera áudio para um arquivo de vídeo, aplicando um prompt negativo para evitar fala.
Args:
video_path (str): Caminho para o vídeo silencioso.
prompt (str): Descrição da cena para guiar a geração de SFX.
duration_seconds (float): Duração do áudio a ser gerado.
Returns:
str: Caminho para o novo arquivo de vídeo com áudio.
"""
if self.net is None:
raise gr.Error("Modelo MMAudio não está carregado. Não é possível gerar áudio.")
logger.info("------------------------------------------------------")
logger.info("--- Gerando Áudio para Fragmento de Vídeo ---")
logger.info(f"--- Vídeo Fragmento: {os.path.basename(video_path)}")
logger.info(f"--- Duração: {duration_seconds:.2f}s")
logger.info(f"--- Prompt (Descrição da Cena): '{prompt}'")
negative_prompt = "human voice"
logger.info(f"--- Negative Prompt: '{negative_prompt}'")
if duration_seconds < 1:
logger.warning("Fragmento muito curto (<1s). Retornando vídeo silencioso.")
logger.info("------------------------------------------------------")
return video_path
if self.device == 'cpu':
logger.warning("Gerando áudio na CPU. Isso pode ser muito lento.")
try:
self.to_gpu()
with torch.no_grad():
rng = torch.Generator(device=self.device).manual_seed(int(time.time()))
fm = FlowMatching(min_sigma=0, inference_mode='euler', num_steps=25)
video_info = load_video(Path(video_path), duration_seconds)
self.seq_cfg.duration = video_info.duration_sec
self.net.update_seq_lengths(self.seq_cfg.latent_seq_len, self.seq_cfg.clip_seq_len, self.seq_cfg.sync_seq_len)
audios = mmaudio_generate(
clip_video=video_info.clip_frames.unsqueeze(0),
sync_video=video_info.sync_frames.unsqueeze(0),
text=[prompt],
negative_text=[negative_prompt],
feature_utils=self.feature_utils,
net=self.net,
fm=fm,
rng=rng,
cfg_strength=4.5
)
audio_waveform = audios.float().cpu()[0]
fragment_name = Path(video_path).stem
output_video_path = os.path.join(self.workspace_dir, f"{fragment_name}_com_audio.mp4")
make_video(video_info, Path(output_video_path), audio_waveform, sampling_rate=self.seq_cfg.sampling_rate)
logger.info(f"--- Fragmento com áudio salvo em: {os.path.basename(output_video_path)}")
logger.info("------------------------------------------------------")
return output_video_path
finally:
self.to_cpu()
# Singleton instantiation
try:
with open("config.yaml", 'r') as f:
config = yaml.safe_load(f)
WORKSPACE_DIR = config['application']['workspace_dir']
audio_specialist_singleton = AudioSpecialist(workspace_dir=WORKSPACE_DIR)
except Exception as e:
logger.error(f"Não foi possível inicializar o AudioSpecialist: {e}", exc_info=True)
audio_specialist_singleton = None