Files
havvk-ComfyUI_AIIA/aiia_audio_debug.py
T
Hawk Lee eff537d9dd feat: add ASR, Podcast Splitter, and Podcast Stitcher nodes for anti-leakage pipeline
- Add AIIA_ASR node with FunASR (paraformer-zh + SenseVoiceSmall)
- Add AIIA_Podcast_Splitter for per-speaker text splitting
- Add AIIA_Podcast_Stitcher with 3-tier fuzzy alignment (exact, Levenshtein, fallback)
- Register 5 previously missing modules in __init__.py
- Update README with installation guide and node documentation
2026-02-15 12:14:45 +08:00

155 lines
5.3 KiB
Python

import torch
import numpy as np
class AIIA_Audio_Splice_Analyzer:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"audio": ("AUDIO",),
},
"optional": {
"splice_info": ("SPLICE_INFO",),
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("spectrogram_image",)
FUNCTION = "analyze_splice"
CATEGORY = "AIIA/Debug"
def analyze_splice(self, audio, splice_info=None):
try:
import matplotlib
import matplotlib.pyplot as plt
import io
from PIL import Image
import torchaudio
except ImportError:
print("[AIIA Debug] Matplotlib not found. Generating error image.")
return (self._create_error_image("Matplotlib not found"),)
waveform = audio["waveform"]
sample_rate = audio["sample_rate"]
# Ensure 2D (channels, time)
if waveform.ndim == 3:
waveform = waveform.squeeze(0) # Remove batch
# Convert to mono for spectrogram
if waveform.shape[0] > 1:
waveform_mono = torch.mean(waveform, dim=0, keepdim=True)
else:
waveform_mono = waveform
# Compute Spectrogram
n_fft = 1024
win_length = None
hop_length = 512
n_mels = 128
mel_spectrogram = torchaudio.transforms.MelSpectrogram(
sample_rate=sample_rate,
n_fft=n_fft,
win_length=win_length,
hop_length=hop_length,
center=True,
pad_mode="reflect",
power=2.0,
norm="slaney",
onesided=True,
n_mels=n_mels,
mel_scale="htk",
)(waveform_mono)
mel_spectrogram_db = torchaudio.transforms.AmplitudeToDB()(mel_spectrogram)
# Plotting
plt.figure(figsize=(12, 6))
plt.imshow(mel_spectrogram_db.squeeze().numpy(), aspect='auto', origin='lower', cmap='viridis')
plt.colorbar(format='%+2.0f dB')
plt.title('Mel Spectrogram with Splice Points')
plt.xlabel('Time (frames)')
plt.ylabel('Frequency (Hz)')
# Create Custom Y-Ticks for Hz
def hz_to_mel_htk(hz):
return 2595 * np.log10(1 + hz / 700)
def mel_to_hz_htk(mel):
return 700 * (10**(mel / 2595) - 1)
# 1. Determine Freq Range
f_min = 0.0
f_max = sample_rate / 2.0
# 2. Convert boundaries to Mel
min_mel = hz_to_mel_htk(f_min)
max_mel = hz_to_mel_htk(f_max)
# 3. Choose tick locations in Hz
# E.g. 0, 500, 1000, 2000, 4000, 8000, 16000...
tick_freqs = [0, 500, 1000, 2000, 4000, 8000, 11025, 16000, 22050]
tick_freqs = [f for f in tick_freqs if f <= f_max] # filter valid
# 4. Convert ticks to Mel Bin Indices
# bin = (mel - min_mel) / (max_mel - min_mel) * (n_mels - 1)
tick_bins = []
tick_labels = []
for f in tick_freqs:
m = hz_to_mel_htk(f)
bin_idx = (m - min_mel) / (max_mel - min_mel) * (n_mels - 1)
tick_bins.append(bin_idx)
tick_labels.append(str(int(f)))
plt.yticks(tick_bins, tick_labels)
# Draw splice lines if info is present
if splice_info and "splice_points" in splice_info:
splice_points = splice_info["splice_points"]
total_samples = splice_info.get("total_samples", waveform.shape[-1])
# Convert sample indices to spectrogram frame indices
# Frame index ~= sample_index / hop_length
for sp in splice_points:
frame_idx = sp / hop_length
plt.axvline(x=frame_idx, color='r', linestyle='--', alpha=0.8, label='Splice Point')
# Add legend strictly once to avoid clutter
if splice_points:
handles, labels = plt.gca().get_legend_handles_labels()
by_label = dict(zip(labels, handles))
plt.legend(by_label.values(), by_label.keys())
# Save to buffer
buf = io.BytesIO()
plt.savefig(buf, format='png', bbox_inches='tight')
plt.close()
buf.seek(0)
# Convert to ComfyUI Image Tensor (Batch, Height, Width, Channels)
image = Image.open(buf).convert('RGB')
image_np = np.array(image).astype(np.float32) / 255.0
image_tensor = torch.from_numpy(image_np).unsqueeze(0)
return (image_tensor,)
def _create_error_image(self, message):
# Create a simple black image with text using PIL if possible, else just black
try:
from PIL import Image, ImageDraw, ImageFont
img = Image.new('RGB', (512, 256), color=(0, 0, 0))
d = ImageDraw.Draw(img)
d.text((10,128), message, fill=(255, 255, 255))
image_np = np.array(img).astype(np.float32) / 255.0
return torch.from_numpy(image_np).unsqueeze(0)
except:
return torch.zeros((1, 256, 512, 3))
NODE_CLASS_MAPPINGS = {
"AIIA_Audio_Splice_Analyzer": AIIA_Audio_Splice_Analyzer
}
NODE_DISPLAY_NAME_MAPPINGS = {
"AIIA_Audio_Splice_Analyzer": "Audio Splice Analyzer (AIIA Debug)"
}