Files

196 lines
7.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
@title: Timbre Audio Loader
@author: ComfyUI-Index-TTS
@description: 用于加载Timbre模型目录下的音频文件的节点
"""
import os
import sys
import hashlib
import torch
import glob
from pathlib import Path
import folder_paths
# # 获取当前目录
current_dir = os.path.dirname(os.path.abspath(__file__))
# if current_dir not in sys.path:
# sys.path.append(current_dir)
def _load_audio_file(file_path):
"""
加载音频文件,返回 (waveform[C, T] float32, sample_rate)。
torchaudio 2.6+ 的 load/save 已迁移到 TorchCodec(Windows 上缺 FFmpeg 时不可用),
所以这里优先用 soundfile(支持 wav/flac/ogg/mp3),失败再用 librosa 兜底。
"""
try:
import soundfile as sf
data, sr = sf.read(file_path, dtype="float32", always_2d=True) # (T, C)
waveform = torch.from_numpy(data.T.copy()) # (C, T)
return waveform, int(sr)
except Exception:
import librosa
data, sr = librosa.load(file_path, sr=None, mono=False)
if data.ndim == 1:
data = data[None, :]
return torch.from_numpy(data), int(sr)
class TimbreAudioLoader:
"""
ComfyUI节点: 从Timbre模型目录加载音频样本文件,支持刷新列表
"""
# 保存扫描的音频文件缓存
audio_files_cache = []
@classmethod
def INPUT_TYPES(cls):
# 定义Timbre模型目录路径 - 使用项目内的目录
timbre_dir = os.path.join(current_dir, "TimbreModel")
# 确保目录存在
os.makedirs(timbre_dir, exist_ok=True)
# 扫描所有支持的音频文件
cls.scan_audio_files(timbre_dir)
return {
"required": {
"audio_file": (cls.audio_files_cache, ),
"refresh": ("BOOLEAN", {"default": False, "label": "刷新音频列表"})
}
}
@classmethod
def scan_audio_files(cls, directory):
"""扫描目录下的所有音频文件"""
# 支持的音频格式模式(Windows不区分大小写)
audio_patterns = ["**/*.wav", "**/*.mp3", "**/*.ogg", "**/*.flac"]
# 初始化音频文件缓存
cls.audio_files_cache = ["无音频文件"] # 默认选项
# 检查目录是否存在
if not os.path.exists(directory):
print(f"[TimbreAudioLoader] 警告: 目录不存在: {directory}")
return
# 使用集合来确保文件名唯一性
unique_filenames = set()
audio_files = []
# 扫描所有音频文件
for pattern in audio_patterns:
# 使用递归模式搜索
matches = glob.glob(os.path.join(directory, pattern), recursive=True)
for file_path in matches:
# 提取文件名(不包含路径)
file_name = os.path.basename(file_path)
# 只添加尚未添加的文件名
if file_name.lower() not in unique_filenames:
unique_filenames.add(file_name.lower())
audio_files.append(file_path)
# 将收集到的文件添加到缓存
if audio_files:
# 按文件名排序
audio_files.sort(key=lambda x: os.path.basename(x).lower())
# 添加文件名到缓存
for file_path in audio_files:
file_name = os.path.basename(file_path)
cls.audio_files_cache.append(file_name)
print(f"[TimbreAudioLoader] 已加载 {len(cls.audio_files_cache)-1} 个音频文件")
else:
print(f"[TimbreAudioLoader] 警告: 未找到音频文件,路径: {directory}")
RETURN_TYPES = ("AUDIO", )
FUNCTION = "load_timbre_audio"
CATEGORY = "audio"
def load_timbre_audio(self, audio_file, refresh):
"""加载选择的音频文件或刷新列表"""
# 定义Timbre模型目录路径 - 使用项目内的目录
timbre_dir = os.path.join(current_dir, "TimbreModel")
# 如果用户点击了刷新按钮
if refresh:
self.__class__.scan_audio_files(timbre_dir)
print("[TimbreAudioLoader] 已刷新音频文件列表")
# 如果选择了"无音频文件"或列表为空,返回空的音频数据
if audio_file == "无音频文件" or not audio_file:
# 创建一个小的空音频样本
waveform = torch.zeros((1, 16000)) # 1秒静音
sample_rate = 16000
return ({"waveform": waveform.unsqueeze(0), "sample_rate": sample_rate}, )
# 构建完整的文件路径
file_path = os.path.join(timbre_dir, audio_file)
try:
# torchaudio 2.6+ 在 Windows 上需要 TorchCodec,改用 soundfile/librosa 加载
waveform, sample_rate = _load_audio_file(file_path)
# 返回ComfyUI音频格式
return ({"waveform": waveform.unsqueeze(0), "sample_rate": sample_rate}, )
except Exception as e:
print(f"[TimbreAudioLoader] 加载音频失败: {e}")
# 发生错误时返回空音频
waveform = torch.zeros((1, 16000))
sample_rate = 16000
return ({"waveform": waveform.unsqueeze(0), "sample_rate": sample_rate}, )
@classmethod
def IS_CHANGED(cls, audio_file, refresh):
"""当输入变化时通知ComfyUI"""
# 如果点击了刷新按钮,返回随机值以触发节点更新
if refresh:
return str(os.urandom(8).hex())
# 如果选择了有效的音频文件,返回文件路径作为变化标识
if audio_file != "无音频文件":
timbre_dir = os.path.join(current_dir, "TimbreModel")
file_path = os.path.join(timbre_dir, audio_file)
# 检查文件是否存在
if os.path.exists(file_path):
# 计算文件哈希值,用于标识变化
m = hashlib.sha256()
with open(file_path, 'rb') as f:
m.update(f.read())
return m.digest().hex()
return audio_file
class RefreshTimbreAudio:
"""
简单的刷新Timbre音频列表节点
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"refresh": ("BOOLEAN", {"default": True, "label": "刷新音频列表"})
}
}
RETURN_TYPES = ()
FUNCTION = "refresh"
CATEGORY = "audio"
OUTPUT_NODE = True
def refresh(self, refresh):
if refresh:
# 定义Timbre模型目录路径(与 TimbreAudioLoader 一致)
timbre_dir = os.path.join(current_dir, "TimbreModel")
# 刷新TimbreAudioLoader中的缓存
TimbreAudioLoader.scan_audio_files(timbre_dir)
print("[RefreshTimbreAudio] 已刷新音频文件列表")
return {}