This commit is contained in:
Li Jian
2025-07-19 22:36:31 +08:00
commit cba05fb103
4 changed files with 398 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
__pycache__
*.pyc
*.pyo
*.pyd
*.pyw
*.pyz
*.pywz
*.pyzw
*.pyzwz
*.pyzwzw
+42
View File
@@ -0,0 +1,42 @@
# ComfyUI WB Audio Duration Node
一个简单的ComfyUI自定义节点,用于获取音频文件的时长。
## 功能
- 支持1D, 2D, 3D音频张量格式
- 支持 `waveform` 和 `audio` 键格式
- 提供详细的调试信息
- 格式化时长显示(秒、分、小时)
## 安装
1. 将此文件夹复制到ComfyUI的 `custom_nodes` 目录
2. 重启ComfyUI
3. 在节点列表中找到 "WB Audio Duration"
## 使用方法
1. 添加 "Load Audio" 节点加载音频文件
2. 添加 "WB Audio Duration" 节点
3. 连接音频输出到时长节点
4. 运行工作流查看结果
## 输出
- `duration_seconds`: 时长(秒,浮点数)
- `duration_text`: 格式化的时长文本
## 支持格式
- 1D张量: `[samples]`
- 2D张量: `[channels, samples]` 或 `[batch, samples]`
- 3D张量: `[batch, channels, samples]`
## 调试信息
节点会在控制台输出详细的调试信息,包括:
- 音频输入类型
- 音频字典键
- 音频数据形状
- 计算过程
+161
View File
@@ -0,0 +1,161 @@
# ComfyUI Audio Duration Node Package
# 直接定义节点类,避免导入问题
import os
import sys
import folder_paths
# 尝试导入torch,如果失败则使用备用方法
try:
import torch
TORCH_AVAILABLE = True
except ImportError:
TORCH_AVAILABLE = False
print("警告: torch未安装,将使用备用方法处理音频数据")
class WB_AudioDurationNode:
def __init__(self):
self.output_dir = folder_paths.get_output_directory()
self.type = "output"
self.output_node = True
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"audio": ("AUDIO",),
},
}
RETURN_TYPES = ("FLOAT", "STRING",)
RETURN_NAMES = ("duration_seconds", "duration_text",)
FUNCTION = "get_duration"
OUTPUT_NODE = True
CATEGORY = "audio"
def get_duration(self, audio):
"""
简化的音频时长获取函数
支持1D, 2D, 3D张量格式
"""
try:
print(f"=== 音频分析开始 ===")
print(f"音频输入类型: {type(audio)}")
# 处理字典格式
if isinstance(audio, dict):
print(f"音频字典键: {list(audio.keys())}")
# 检查是否有必要的键 - 支持两种格式
if ('waveform' in audio and 'sample_rate' in audio) or ('audio' in audio and 'sample_rate' in audio):
# 优先使用waveform,如果没有则使用audio
if 'waveform' in audio:
audio_data = audio['waveform']
print("使用waveform键")
else:
audio_data = audio['audio']
print("使用audio键")
sample_rate = audio['sample_rate']
print(f"音频数据类型: {type(audio_data)}")
print(f"采样率: {sample_rate}")
# 尝试获取音频长度
if hasattr(audio_data, 'shape'):
print(f"音频数据形状: {audio_data.shape}")
# 计算样本数 - 支持1D, 2D, 3D张量
if len(audio_data.shape) == 1:
# 1D: [samples]
num_samples = audio_data.shape[0]
print("处理1D张量")
elif len(audio_data.shape) == 2:
# 2D: [channels, samples] 或 [batch, samples]
num_samples = audio_data.shape[1]
print("处理2D张量")
elif len(audio_data.shape) == 3:
# 3D: [batch, channels, samples]
num_samples = audio_data.shape[2]
print("处理3D张量")
else:
return (0.0, f"不支持的形状: {audio_data.shape}")
# 计算时长
duration_seconds = num_samples / sample_rate
duration_text = self.format_duration(duration_seconds)
print(f"样本数: {num_samples}")
print(f"计算出的时长: {duration_seconds}秒")
print(f"格式化时长: {duration_text}")
return (duration_seconds, duration_text)
elif hasattr(audio_data, '__len__'):
# 如果数据有长度属性
num_samples = len(audio_data)
duration_seconds = num_samples / sample_rate
duration_text = self.format_duration(duration_seconds)
print(f"使用长度属性,样本数: {num_samples}")
print(f"计算出的时长: {duration_seconds}秒")
return (duration_seconds, duration_text)
else:
return (0.0, f"无法获取音频数据长度,类型: {type(audio_data)}")
else:
return (0.0, f"音频字典缺少必要键,当前键: {list(audio.keys())}")
# 处理字符串(文件路径)
elif isinstance(audio, str):
print(f"音频文件路径: {audio}")
if os.path.exists(audio):
# 简单估算
file_size = os.path.getsize(audio)
estimated_duration = (file_size * 8) / (128 * 1000) # 假设128kbps
duration_text = self.format_duration(estimated_duration)
return (estimated_duration, f"估算时长: {duration_text}")
else:
return (0.0, f"文件不存在: {audio}")
else:
return (0.0, f"不支持的音频输入类型: {type(audio)}")
except Exception as e:
import traceback
print(f"=== 错误详情 ===")
print(f"错误类型: {type(e)}")
print(f"错误信息: {str(e)}")
print(f"错误堆栈: {traceback.format_exc()}")
return (0.0, f"处理错误: {str(e)}")
def format_duration(self, seconds):
"""
格式化时长显示
"""
if seconds < 1:
return f"{seconds:.3f}秒"
elif seconds < 60:
return f"{seconds:.2f}秒"
elif seconds < 3600:
minutes = int(seconds // 60)
remaining_seconds = seconds % 60
return f"{minutes}分{remaining_seconds:.2f}秒"
else:
hours = int(seconds // 3600)
remaining_minutes = int((seconds % 3600) // 60)
remaining_seconds = seconds % 60
return f"{hours}小时{remaining_minutes}分{remaining_seconds:.2f}秒"
# 节点注册
NODE_CLASS_MAPPINGS = {
"WB_AudioDuration": WB_AudioDurationNode
}
NODE_DISPLAY_NAME_MAPPINGS = {
"WB_AudioDuration": "WB Audio Duration"
}
print("ComfyUI WB Audio Duration Node 已加载")
+185
View File
@@ -0,0 +1,185 @@
{
"id": "dfb2747d-8793-4209-b89b-c92284762d7d",
"revision": 0,
"last_node_id": 6,
"last_link_id": 4,
"nodes": [
{
"id": 2,
"type": "LoadAudio",
"pos": [
1446.5673828125,
-2019.3924560546875
],
"size": [
270,
136
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "AUDIO",
"type": "AUDIO",
"links": [
1
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.44",
"Node name for S&R": "LoadAudio"
},
"widgets_values": [
"song.mp3",
null,
null
]
},
{
"id": 3,
"type": "WB_AudioDuration",
"pos": [
1782.639892578125,
-1973.1875
],
"size": [
169.095703125,
46
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "audio",
"type": "AUDIO",
"link": 1
}
],
"outputs": [
{
"name": "duration_seconds",
"type": "FLOAT",
"links": [
3
]
},
{
"name": "duration_text",
"type": "STRING",
"links": [
4
]
}
],
"properties": {
"Node name for S&R": "WB_AudioDuration"
}
},
{
"id": 5,
"type": "PreviewAny",
"pos": [
2064.296875,
-2021.0582275390625
],
"size": [
140,
76
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "source",
"type": "*",
"link": 3
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.44",
"Node name for S&R": "PreviewAny"
},
"widgets_values": []
},
{
"id": 6,
"type": "PreviewAny",
"pos": [
2060.477294921875,
-1892.009521484375
],
"size": [
140,
76
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "source",
"type": "*",
"link": 4
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.44",
"Node name for S&R": "PreviewAny"
},
"widgets_values": []
}
],
"links": [
[
1,
2,
0,
3,
0,
"AUDIO"
],
[
3,
3,
0,
5,
0,
"*"
],
[
4,
3,
1,
6,
0,
"*"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1.7088742511245891,
"offset": [
-1331.3379874532752,
2114.1241784595736
]
},
"frontendVersion": "1.23.4",
"VHS_latentpreview": false,
"VHS_latentpreviewrate": 0,
"VHS_MetadataImage": true,
"VHS_KeepIntermediate": true
},
"version": 0.4
}