155 lines
5.3 KiB
Python
155 lines
5.3 KiB
Python
import torch
|
|
import numpy as np
|
|
import soundfile as sf
|
|
import soxr
|
|
import tempfile
|
|
import os
|
|
import sys
|
|
|
|
# 确保当前目录在 Python 路径的最前面
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
if current_dir in sys.path:
|
|
sys.path.remove(current_dir)
|
|
sys.path.insert(0, current_dir)
|
|
# 导入前再次确认路径
|
|
print(f"[ClearVoice Nodes] Current dir: {current_dir}")
|
|
print(f"[ClearVoice Nodes] sys.path[0]: {sys.path[0]}")
|
|
|
|
from .clearvoice import ClearVoice
|
|
|
|
|
|
class ClearVoiceModelLoader:
|
|
"""加载 ClearVoice 模型的节点"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"model_name": (["MossFormer2_SS_16K"], {"default": "MossFormer2_SS_16K"}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("CLEARVOICE_MODEL",)
|
|
RETURN_NAMES = ("model",)
|
|
FUNCTION = "load_model"
|
|
CATEGORY = "audio/clearvoice"
|
|
|
|
def load_model(self, model_name):
|
|
print(f"Initializing ClearVoice model: {model_name}...")
|
|
|
|
# 保存当前工作目录
|
|
original_cwd = os.getcwd()
|
|
|
|
# 切换到 ClearVoice 所在的目录
|
|
clearvoice_dir = os.path.dirname(os.path.abspath(__file__))
|
|
os.chdir(clearvoice_dir)
|
|
|
|
try:
|
|
cv_model = ClearVoice(
|
|
task='speech_separation',
|
|
model_names=[model_name]
|
|
)
|
|
print("Model initialized successfully")
|
|
return (cv_model,)
|
|
finally:
|
|
# 恢复原来的工作目录
|
|
os.chdir(original_cwd)
|
|
|
|
|
|
class ClearVoiceSpeechSeparation:
|
|
"""使用 ClearVoice 模型进行语音分离的节点"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"model": ("CLEARVOICE_MODEL",),
|
|
"audio": ("AUDIO",),
|
|
"target_sample_rate": ([8000, 16000], {"default": 16000}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("AUDIO", "AUDIO")
|
|
RETURN_NAMES = ("separated_audio_1", "separated_audio_2")
|
|
FUNCTION = "separate_audio"
|
|
CATEGORY = "audio/clearvoice"
|
|
|
|
def separate_audio(self, model, audio, target_sample_rate=16000):
|
|
# 从 ComfyUI AUDIO 格式中提取数据
|
|
# AUDIO 格式通常是: {"waveform": tensor, "sample_rate": int}
|
|
waveform = audio["waveform"] # shape: [batch, channels, samples]
|
|
sample_rate = audio["sample_rate"]
|
|
|
|
# 转换为 numpy 数组
|
|
if isinstance(waveform, torch.Tensor):
|
|
audio_np = waveform.squeeze(0).cpu().numpy() # [channels, samples]
|
|
else:
|
|
audio_np = np.array(waveform).squeeze(0)
|
|
|
|
# 如果是立体声,转换为单声道
|
|
if len(audio_np.shape) > 1 and audio_np.shape[0] > 1:
|
|
print("Converting stereo to mono...")
|
|
audio_np = audio_np.mean(axis=0) # 取平均值转为单声道
|
|
elif len(audio_np.shape) > 1:
|
|
audio_np = audio_np[0] # 取第一个声道
|
|
|
|
# 重采样到目标采样率
|
|
if sample_rate != target_sample_rate:
|
|
print(f"Resampling audio from {sample_rate} Hz to {target_sample_rate} Hz...")
|
|
audio_np = soxr.resample(audio_np, sample_rate, target_sample_rate)
|
|
|
|
# 创建临时文件保存音频
|
|
with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as temp_file:
|
|
temp_path = temp_file.name
|
|
sf.write(temp_path, audio_np, target_sample_rate)
|
|
|
|
try:
|
|
# 使用 ClearVoice 进行语音分离
|
|
print("Separating audio...")
|
|
output_wav_dict = model(input_path=temp_path, online_write=False)
|
|
|
|
# 提取分离后的音频
|
|
if isinstance(output_wav_dict, dict):
|
|
key = next(iter(output_wav_dict))
|
|
output_wav_list = output_wav_dict[key]
|
|
output_wav_s1 = output_wav_list[0]
|
|
output_wav_s2 = output_wav_list[1]
|
|
else:
|
|
output_wav_list = output_wav_dict
|
|
output_wav_s1 = output_wav_list[0]
|
|
output_wav_s2 = output_wav_list[1]
|
|
|
|
# 转换为 ComfyUI AUDIO 格式
|
|
# output_wav_s1 和 s2 的 shape 应该是 [1, samples]
|
|
audio_1_tensor = torch.from_numpy(output_wav_s1).float().unsqueeze(0) # [1, 1, samples]
|
|
audio_2_tensor = torch.from_numpy(output_wav_s2).float().unsqueeze(0) # [1, 1, samples]
|
|
|
|
audio_output_1 = {
|
|
"waveform": audio_1_tensor,
|
|
"sample_rate": target_sample_rate
|
|
}
|
|
|
|
audio_output_2 = {
|
|
"waveform": audio_2_tensor,
|
|
"sample_rate": target_sample_rate
|
|
}
|
|
|
|
print("Audio separation completed successfully")
|
|
return (audio_output_1, audio_output_2)
|
|
|
|
finally:
|
|
# 清理临时文件
|
|
if os.path.exists(temp_path):
|
|
os.remove(temp_path)
|
|
|
|
|
|
# ComfyUI 节点注册
|
|
NODE_CLASS_MAPPINGS = {
|
|
"ClearVoiceModelLoader": ClearVoiceModelLoader,
|
|
"ClearVoiceSpeechSeparation": ClearVoiceSpeechSeparation
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"ClearVoiceModelLoader": "Load ClearVoice Model",
|
|
"ClearVoiceSpeechSeparation": "ClearVoice Speech Separation"
|
|
} |