change ui

This commit is contained in:
AIFSH
2024-04-11 00:44:03 +00:00
parent e56ff3e0fe
commit 5713448015
19 changed files with 365 additions and 901 deletions
+3 -3
View File
@@ -1,5 +1,5 @@
WEB_DIRECTORY = "./web"
from .nodes import LoadAudio, UVR5,PlayAudio
from .nodes import LoadAudio, UVR5,PreViewAudio
# Set the web directory, any .js file in that directory will be loaded by the frontend as a frontend extension
# WEB_DIRECTORY = "./somejs"
@@ -9,12 +9,12 @@ from .nodes import LoadAudio, UVR5,PlayAudio
NODE_CLASS_MAPPINGS = {
"UVR5_Node": UVR5,
"LoadAudio": LoadAudio,
"PlayAudio": PlayAudio
"PreViewAudio": PreViewAudio
}
# A dictionary that contains the friendly/humanly readable titles for the nodes
NODE_DISPLAY_NAME_MAPPINGS = {
"UVR5_Node": "UVR5 Node",
"LoadAudio": "AudioLoader",
"PlayAudio": "PlayAudio"
"PreViewAudio": "PreView Audio"
}
Binary file not shown.
Binary file not shown.
+4 -3
View File
@@ -17,7 +17,8 @@ weights_path = os.path.join(node_path, "uvr5")
device= "cuda" if cuda_malloc_supported() else "cpu"
is_half=True
class PlayAudio:
class PreViewAudio:
@classmethod
def INPUT_TYPES(s):
return {"required":
@@ -53,7 +54,7 @@ class LoadAudio:
@classmethod
def INPUT_TYPES(s):
input_dir = input_path
files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f)) and f.split('.')[-1] in ["wav", "mp3"]]
files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f)) and f.split('.')[-1] in ["wav", "mp3","WAV","flac","m4a"]]
return {"required":
{"audio": (sorted(files),)},
}
@@ -142,7 +143,7 @@ class UVR5:
"display": "slider"
}),
"format0":(["wav", "flac", "mp3", "m4a"],{
"default": "flac"
"default": "wav"
})
},
}
@@ -1,261 +0,0 @@
import os
import logging
logger = logging.getLogger(__name__)
import librosa
import numpy as np
import soundfile as sf
import torch
from tqdm import tqdm
from cuda_malloc import cuda_malloc_supported
cpu = torch.device("cpu" if not cuda_malloc_supported() else "cuda")
class ConvTDFNetTrim:
def __init__(
self, device, model_name, target_name, L, dim_f, dim_t, n_fft, hop=1024
):
super(ConvTDFNetTrim, self).__init__()
self.dim_f = dim_f
self.dim_t = 2**dim_t
self.n_fft = n_fft
self.hop = hop
self.n_bins = self.n_fft // 2 + 1
self.chunk_size = hop * (self.dim_t - 1)
self.window = torch.hann_window(window_length=self.n_fft, periodic=True).to(
device
)
self.target_name = target_name
self.blender = "blender" in model_name
self.dim_c = 4
out_c = self.dim_c * 4 if target_name == "*" else self.dim_c
self.freq_pad = torch.zeros(
[1, out_c, self.n_bins - self.dim_f, self.dim_t]
).to(device)
self.n = L // 2
def stft(self, x):
x = x.reshape([-1, self.chunk_size])
x = torch.stft(
x,
n_fft=self.n_fft,
hop_length=self.hop,
window=self.window,
center=True,
return_complex=True,
)
x = torch.view_as_real(x)
x = x.permute([0, 3, 1, 2])
x = x.reshape([-1, 2, 2, self.n_bins, self.dim_t]).reshape(
[-1, self.dim_c, self.n_bins, self.dim_t]
)
return x[:, :, : self.dim_f]
def istft(self, x, freq_pad=None):
freq_pad = (
self.freq_pad.repeat([x.shape[0], 1, 1, 1])
if freq_pad is None
else freq_pad
)
x = torch.cat([x, freq_pad], -2)
c = 4 * 2 if self.target_name == "*" else 2
x = x.reshape([-1, c, 2, self.n_bins, self.dim_t]).reshape(
[-1, 2, self.n_bins, self.dim_t]
)
x = x.permute([0, 2, 3, 1])
x = x.contiguous()
x = torch.view_as_complex(x)
x = torch.istft(
x, n_fft=self.n_fft, hop_length=self.hop, window=self.window, center=True
)
return x.reshape([-1, c, self.chunk_size])
def get_models(device, dim_f, dim_t, n_fft):
return ConvTDFNetTrim(
device=device,
model_name="Conv-TDF",
target_name="vocals",
L=11,
dim_f=dim_f,
dim_t=dim_t,
n_fft=n_fft,
)
class Predictor:
def __init__(self, args):
import onnxruntime as ort
logger.info(ort.get_available_providers())
self.args = args
self.model_ = get_models(
device=cpu, dim_f=args.dim_f, dim_t=args.dim_t, n_fft=args.n_fft
)
self.model = ort.InferenceSession(
os.path.join(args.onnx, self.model_.target_name + ".onnx"),
providers=[
"CUDAExecutionProvider",
"DmlExecutionProvider",
"CPUExecutionProvider",
],
)
logger.info("ONNX load done")
def demix(self, mix):
samples = mix.shape[-1]
margin = self.args.margin
chunk_size = self.args.chunks * 44100
assert not margin == 0, "margin cannot be zero!"
if margin > chunk_size:
margin = chunk_size
segmented_mix = {}
if self.args.chunks == 0 or samples < chunk_size:
chunk_size = samples
counter = -1
for skip in range(0, samples, chunk_size):
counter += 1
s_margin = 0 if counter == 0 else margin
end = min(skip + chunk_size + margin, samples)
start = skip - s_margin
segmented_mix[skip] = mix[:, start:end].copy()
if end == samples:
break
sources = self.demix_base(segmented_mix, margin_size=margin)
"""
mix:(2,big_sample)
segmented_mix:offset->(2,small_sample)
sources:(1,2,big_sample)
"""
return sources
def demix_base(self, mixes, margin_size):
chunked_sources = []
progress_bar = tqdm(total=len(mixes))
progress_bar.set_description("Processing")
for mix in mixes:
cmix = mixes[mix]
sources = []
n_sample = cmix.shape[1]
model = self.model_
trim = model.n_fft // 2
gen_size = model.chunk_size - 2 * trim
pad = gen_size - n_sample % gen_size
mix_p = np.concatenate(
(np.zeros((2, trim)), cmix, np.zeros((2, pad)), np.zeros((2, trim))), 1
)
mix_waves = []
i = 0
while i < n_sample + pad:
waves = np.array(mix_p[:, i : i + model.chunk_size])
mix_waves.append(waves)
i += gen_size
mix_waves = torch.tensor(mix_waves, dtype=torch.float32).to(cpu)
with torch.no_grad():
_ort = self.model
spek = model.stft(mix_waves)
if self.args.denoise:
spec_pred = (
-_ort.run(None, {"input": -spek.cpu().numpy()})[0] * 0.5
+ _ort.run(None, {"input": spek.cpu().numpy()})[0] * 0.5
)
tar_waves = model.istft(torch.tensor(spec_pred).to(cpu))
else:
tar_waves = model.istft(
torch.tensor(_ort.run(None, {"input": spek.cpu().numpy()})[0]).to(cpu)
)
tar_signal = (
tar_waves[:, :, trim:-trim]
.transpose(0, 1)
.reshape(2, -1)
.cpu()
.numpy()[:, :-pad]
)
start = 0 if mix == 0 else margin_size
end = None if mix == list(mixes.keys())[::-1][0] else -margin_size
if margin_size == 0:
end = None
sources.append(tar_signal[:, start:end])
progress_bar.update(1)
chunked_sources.append(sources)
_sources = np.concatenate(chunked_sources, axis=-1)
# del self.model
progress_bar.close()
return _sources
def prediction(self, m, vocal_root, others_root, format):
os.makedirs(vocal_root, exist_ok=True)
os.makedirs(others_root, exist_ok=True)
basename = os.path.basename(m)
mix, rate = librosa.load(m, mono=False, sr=44100)
if mix.ndim == 1:
mix = np.asfortranarray([mix, mix])
mix = mix.T
sources = self.demix(mix.T)
opt = sources[0].T
if format in ["wav", "flac"]:
vocal_AUDIO = "%s/%s_main_vocal.%s" % (vocal_root, basename, format)
sf.write(
vocal_AUDIO, mix - opt, rate
)
bgm_AUDIO = "%s/%s_others.%s" % (others_root, basename, format)
sf.write(bgm_AUDIO, opt, rate)
else:
vocal_AUDIO = "%s/%s_main_vocal.wav" % (vocal_root, basename)
bgm_AUDIO = "%s/%s_others.wav" % (others_root, basename)
sf.write(vocal_AUDIO, mix - opt, rate)
sf.write(bgm_AUDIO, opt, rate)
opt_path_vocal = vocal_AUDIO[:-4] + ".%s" % format
opt_path_other = bgm_AUDIO[:-4] + ".%s" % format
if os.path.exists(vocal_AUDIO):
os.system(
"ffmpeg -i %s -vn %s -q:a 2 -y" % (vocal_AUDIO, opt_path_vocal)
)
if os.path.exists(opt_path_vocal):
try:
os.remove(vocal_AUDIO)
except:
pass
if os.path.exists(bgm_AUDIO):
os.system(
"ffmpeg -i %s -vn %s -q:a 2 -y" % (bgm_AUDIO, opt_path_other)
)
if os.path.exists(opt_path_other):
try:
os.remove(bgm_AUDIO)
except:
pass
return vocal_AUDIO,bgm_AUDIO
class MDXNetDereverb:
def __init__(self, chunks):
self.onnx = "%s/uvr5_weights/onnx_dereverb_By_FoxJoy"%os.path.dirname(os.path.abspath(__file__))
self.shifts = 10 # 'Predict with randomised equivariant stabilisation'
self.mixing = "min_mag" # ['default','min_mag','max_mag']
self.chunks = chunks
self.margin = 44100
self.dim_t = 9
self.dim_f = 3072
self.n_fft = 6144
self.denoise = True
self.pred = Predictor(self)
self.device = cpu
def _path_audio_(self, input, others_root, vocal_root, format, is_hp3=False):
return self.pred.prediction(input, vocal_root, others_root, format)
-378
View File
@@ -1,378 +0,0 @@
import os,sys
parent_directory = os.path.dirname(os.path.abspath(__file__))
import logging,pdb
logger = logging.getLogger(__name__)
import librosa
import numpy as np
import soundfile as sf
import torch
from .lib.lib_v5 import nets_61968KB as Nets
from .lib.lib_v5 import spec_utils
from .lib.lib_v5.model_param_init import ModelParameters
from .lib.lib_v5.nets_new import CascadedNet
from .lib.utils import inference
class AudioPre:
def __init__(self, agg, model_path, device, is_half, tta=False):
self.model_path = model_path
self.device = device
self.data = {
# Processing Options
"postprocess": False,
"tta": tta,
# Constants
"window_size": 512,
"agg": agg,
"high_end_process": "mirroring",
}
mp = ModelParameters("%s/lib/lib_v5/modelparams/4band_v2.json"%parent_directory)
model = Nets.CascadedASPPNet(mp.param["bins"] * 2)
cpk = torch.load(model_path, map_location="cpu")
model.load_state_dict(cpk)
model.eval()
if is_half:
model = model.half().to(device)
else:
model = model.to(device)
self.mp = mp
self.model = model
def _path_audio_(
self, music_file, ins_root=None, vocal_root=None, format="flac", is_hp3=False
):
if ins_root is None and vocal_root is None:
return "No save root."
name = os.path.basename(music_file)
if ins_root is not None:
os.makedirs(ins_root, exist_ok=True)
if vocal_root is not None:
os.makedirs(vocal_root, exist_ok=True)
X_wave, y_wave, X_spec_s, y_spec_s = {}, {}, {}, {}
bands_n = len(self.mp.param["band"])
# print(bands_n)
for d in range(bands_n, 0, -1):
bp = self.mp.param["band"][d]
if d == bands_n: # high-end band
(
X_wave[d],
_,
) = librosa.core.load( # 理论上librosa读取可能对某些音频有bug,应该上ffmpeg读取,但是太麻烦了弃坑
music_file,
sr = bp["sr"],
mono = False,
dtype = np.float32,
res_type = bp["res_type"],
)
if X_wave[d].ndim == 1:
X_wave[d] = np.asfortranarray([X_wave[d], X_wave[d]])
else: # lower bands
X_wave[d] = librosa.core.resample(
X_wave[d + 1],
orig_sr = self.mp.param["band"][d + 1]["sr"],
target_sr = bp["sr"],
res_type = bp["res_type"],
)
# Stft of wave source
X_spec_s[d] = spec_utils.wave_to_spectrogram_mt(
X_wave[d],
bp["hl"],
bp["n_fft"],
self.mp.param["mid_side"],
self.mp.param["mid_side_b2"],
self.mp.param["reverse"],
)
# pdb.set_trace()
if d == bands_n and self.data["high_end_process"] != "none":
input_high_end_h = (bp["n_fft"] // 2 - bp["crop_stop"]) + (
self.mp.param["pre_filter_stop"] - self.mp.param["pre_filter_start"]
)
input_high_end = X_spec_s[d][
:, bp["n_fft"] // 2 - input_high_end_h : bp["n_fft"] // 2, :
]
X_spec_m = spec_utils.combine_spectrograms(X_spec_s, self.mp)
aggresive_set = float(self.data["agg"] / 100)
aggressiveness = {
"value": aggresive_set,
"split_bin": self.mp.param["band"][1]["crop_stop"],
}
with torch.no_grad():
pred, X_mag, X_phase = inference(
X_spec_m, self.device, self.model, aggressiveness, self.data
)
# Postprocess
if self.data["postprocess"]:
pred_inv = np.clip(X_mag - pred, 0, np.inf)
pred = spec_utils.mask_silence(pred, pred_inv)
y_spec_m = pred * X_phase
v_spec_m = X_spec_m - y_spec_m
if is_hp3 == True:
ins_root,vocal_root = vocal_root,ins_root
if ins_root is not None:
if self.data["high_end_process"].startswith("mirroring"):
input_high_end_ = spec_utils.mirroring(
self.data["high_end_process"], y_spec_m, input_high_end, self.mp
)
wav_instrument = spec_utils.cmb_spectrogram_to_wave(
y_spec_m, self.mp, input_high_end_h, input_high_end_
)
else:
wav_instrument = spec_utils.cmb_spectrogram_to_wave(y_spec_m, self.mp)
logger.info("%s instruments done" % name)
if is_hp3 == True:
head = "vocal_"
else:
head = "instrument_"
if format in ["wav", "flac"]:
bgm_AUDIO = os.path.join(
ins_root,
head + "{}_{}.{}".format(name, self.data["agg"], format),
)
sf.write(
bgm_AUDIO,
(np.array(wav_instrument) * 32768).astype("int16"),
self.mp.param["sr"],
) #
else:
bgm_AUDIO = os.path.join(
ins_root, head + "{}_{}.wav".format(name, self.data["agg"])
)
sf.write(
bgm_AUDIO,
(np.array(wav_instrument) * 32768).astype("int16"),
self.mp.param["sr"],
)
if os.path.exists(bgm_AUDIO):
opt_format_path = bgm_AUDIO[:-4] + ".%s" % format
os.system("ffmpeg -i %s -vn %s -q:a 2 -y" % (bgm_AUDIO, opt_format_path))
if os.path.exists(opt_format_path):
try:
os.remove(bgm_AUDIO)
except:
pass
if vocal_root is not None:
if is_hp3 == True:
head = "instrument_"
else:
head = "vocal_"
if self.data["high_end_process"].startswith("mirroring"):
input_high_end_ = spec_utils.mirroring(
self.data["high_end_process"], v_spec_m, input_high_end, self.mp
)
wav_vocals = spec_utils.cmb_spectrogram_to_wave(
v_spec_m, self.mp, input_high_end_h, input_high_end_
)
else:
wav_vocals = spec_utils.cmb_spectrogram_to_wave(v_spec_m, self.mp)
logger.info("%s vocals done" % name)
if format in ["wav", "flac"]:
vocal_AUDIO = os.path.join(
vocal_root,
head + "{}_{}.{}".format(name, self.data["agg"], format),
)
sf.write(
vocal_AUDIO,
(np.array(wav_vocals) * 32768).astype("int16"),
self.mp.param["sr"],
)
else:
vocal_AUDIO = os.path.join(
vocal_root, head + "{}_{}.wav".format(name, self.data["agg"])
)
sf.write(
vocal_AUDIO,
(np.array(wav_vocals) * 32768).astype("int16"),
self.mp.param["sr"],
)
if os.path.exists(vocal_AUDIO):
opt_format_path = vocal_AUDIO[:-4] + ".%s" % format
os.system("ffmpeg -i %s -vn %s -q:a 2 -y" % (vocal_AUDIO, opt_format_path))
if os.path.exists(opt_format_path):
try:
os.remove(vocal_AUDIO)
except:
pass
if is_hp3 == True:
return bgm_AUDIO,vocal_AUDIO
return vocal_AUDIO, bgm_AUDIO
class AudioPreDeEcho:
def __init__(self, agg, model_path, device, is_half, tta=False):
self.model_path = model_path
self.device = device
self.data = {
# Processing Options
"postprocess": False,
"tta": tta,
# Constants
"window_size": 512,
"agg": agg,
"high_end_process": "mirroring",
}
mp = ModelParameters("%s/lib/lib_v5/modelparams/4band_v3.json"%parent_directory)
nout = 64 if "DeReverb" in model_path else 48
model = CascadedNet(mp.param["bins"] * 2, nout)
cpk = torch.load(model_path, map_location="cpu")
model.load_state_dict(cpk)
model.eval()
if is_half:
model = model.half().to(device)
else:
model = model.to(device)
self.mp = mp
self.model = model
def _path_audio_(
self, music_file, vocal_root=None, ins_root=None, format="flac", is_hp3=False
): # 3个VR模型vocal和ins是反的
if ins_root is None and vocal_root is None:
return "No save root."
name = os.path.basename(music_file)
if ins_root is not None:
os.makedirs(ins_root, exist_ok=True)
if vocal_root is not None:
os.makedirs(vocal_root, exist_ok=True)
X_wave, y_wave, X_spec_s, y_spec_s = {}, {}, {}, {}
bands_n = len(self.mp.param["band"])
# print(bands_n)
for d in range(bands_n, 0, -1):
bp = self.mp.param["band"][d]
if d == bands_n: # high-end band
(
X_wave[d],
_,
) = librosa.core.load( # 理论上librosa读取可能对某些音频有bug,应该上ffmpeg读取,但是太麻烦了弃坑
music_file,
sr = bp["sr"],
mono = False,
dtype = np.float32,
res_type = bp["res_type"],
)
if X_wave[d].ndim == 1:
X_wave[d] = np.asfortranarray([X_wave[d], X_wave[d]])
else: # lower bands
X_wave[d] = librosa.core.resample(
X_wave[d + 1],
orig_sr = self.mp.param["band"][d + 1]["sr"],
target_sr = bp["sr"],
res_type = bp["res_type"],
)
# Stft of wave source
X_spec_s[d] = spec_utils.wave_to_spectrogram_mt(
X_wave[d],
bp["hl"],
bp["n_fft"],
self.mp.param["mid_side"],
self.mp.param["mid_side_b2"],
self.mp.param["reverse"],
)
# pdb.set_trace()
if d == bands_n and self.data["high_end_process"] != "none":
input_high_end_h = (bp["n_fft"] // 2 - bp["crop_stop"]) + (
self.mp.param["pre_filter_stop"] - self.mp.param["pre_filter_start"]
)
input_high_end = X_spec_s[d][
:, bp["n_fft"] // 2 - input_high_end_h : bp["n_fft"] // 2, :
]
X_spec_m = spec_utils.combine_spectrograms(X_spec_s, self.mp)
aggresive_set = float(self.data["agg"] / 100)
aggressiveness = {
"value": aggresive_set,
"split_bin": self.mp.param["band"][1]["crop_stop"],
}
with torch.no_grad():
pred, X_mag, X_phase = inference(
X_spec_m, self.device, self.model, aggressiveness, self.data
)
# Postprocess
if self.data["postprocess"]:
pred_inv = np.clip(X_mag - pred, 0, np.inf)
pred = spec_utils.mask_silence(pred, pred_inv)
y_spec_m = pred * X_phase
v_spec_m = X_spec_m - y_spec_m
if ins_root is not None:
if self.data["high_end_process"].startswith("mirroring"):
input_high_end_ = spec_utils.mirroring(
self.data["high_end_process"], y_spec_m, input_high_end, self.mp
)
wav_instrument = spec_utils.cmb_spectrogram_to_wave(
y_spec_m, self.mp, input_high_end_h, input_high_end_
)
else:
wav_instrument = spec_utils.cmb_spectrogram_to_wave(y_spec_m, self.mp)
logger.info("%s instruments done" % name)
if format in ["wav", "flac"]:
bgm_AUDIO = os.path.join(
ins_root,
"vocal_{}_{}.{}".format(name, self.data["agg"], format),
)
sf.write(
bgm_AUDIO,
(np.array(wav_instrument) * 32768).astype("int16"),
self.mp.param["sr"],
) #
else:
bgm_AUDIO = os.path.join(
ins_root, "vocal_{}_{}.wav".format(name, self.data["agg"])
)
sf.write(
bgm_AUDIO,
(np.array(wav_instrument) * 32768).astype("int16"),
self.mp.param["sr"],
)
if os.path.exists(bgm_AUDIO):
opt_format_path = bgm_AUDIO[:-4] + ".%s" % format
os.system("ffmpeg -i %s -vn %s -q:a 2 -y" % (bgm_AUDIO, opt_format_path))
if os.path.exists(opt_format_path):
try:
os.remove(bgm_AUDIO)
except:
pass
if vocal_root is not None:
if self.data["high_end_process"].startswith("mirroring"):
input_high_end_ = spec_utils.mirroring(
self.data["high_end_process"], v_spec_m, input_high_end, self.mp
)
wav_vocals = spec_utils.cmb_spectrogram_to_wave(
v_spec_m, self.mp, input_high_end_h, input_high_end_
)
else:
wav_vocals = spec_utils.cmb_spectrogram_to_wave(v_spec_m, self.mp)
logger.info("%s vocals done" % name)
if format in ["wav", "flac"]:
vocal_AUDIO = os.path.join(
vocal_root,
"instrument_{}_{}.{}".format(name, self.data["agg"], format),
)
sf.write(
vocal_AUDIO,
(np.array(wav_vocals) * 32768).astype("int16"),
self.mp.param["sr"],
)
else:
vocal_AUDIO = os.path.join(
vocal_root, "instrument_{}_{}.wav".format(name, self.data["agg"])
)
sf.write(
vocal_AUDIO,
(np.array(wav_vocals) * 32768).astype("int16"),
self.mp.param["sr"],
)
if os.path.exists(vocal_AUDIO):
opt_format_path = vocal_AUDIO[:-4] + ".%s" % format
os.system("ffmpeg -i %s -vn %s -q:a 2 -y" % (vocal_AUDIO, opt_format_path))
if os.path.exists(opt_format_path):
try:
os.remove(vocal_AUDIO)
except:
pass
return bgm_AUDIO,vocal_AUDIO
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-171
View File
@@ -1,171 +0,0 @@
import { app } from "../../../scripts/app.js";
import { api } from '../../../scripts/api.js'
import { ComfyWidgets } from "../../../scripts/widgets.js"
function audioUpload(node, inputName, inputData, app) {
const audioWidget = node.widgets.find((w) => w.name === "audio");
let uploadWidget;
/*
A method that returns the required style for the html
*/
function playAudio(node, name) {
let url = `/view?filename=${encodeURIComponent(name)}&type=input&subfolder=${app.getPreviewFormatParam()}${app.getRandParam()}`
while (node.widgets.length > 2){
node.widgets.pop()
}
let isTick = true;
const audio = new Audio(url);
const slider = node.addWidget(
"slider",
"loading",
0,
(v) => {
if (!isTick) {
audio.currentTime = v;
}
isTick = false;
},
{
min: 0,
max: 0,
}
);
const button = node.addWidget("button", `Play ${name}`, "play", () => {
try {
if (audio.paused) {
audio.play();
button.name = `Pause ${name}`;
} else {
audio.pause();
button.name = `Play ${name}`;
}
} catch (error) {
alert(error);
}
app.canvas.setDirty(true);
});
audio.addEventListener("timeupdate", () => {
isTick = true;
slider.value = audio.currentTime;
app.canvas.setDirty(true);
});
audio.addEventListener("ended", () => {
button.name = `Play ${name}`;
app.canvas.setDirty(true);
});
audio.addEventListener("loadedmetadata", () => {
slider.options.max = audio.duration;
slider.name = `(${audio.duration})`;
app.canvas.setDirty(true);
});
}
var default_value = audioWidget.value;
Object.defineProperty(audioWidget, "value", {
set : function(value) {
this._real_value = value;
},
get : function() {
let value = "";
if (this._real_value) {
value = this._real_value;
} else {
return default_value;
}
if (value.filename) {
let real_value = value;
value = "";
if (real_value.subfolder) {
value = real_value.subfolder + "/";
}
value += real_value.filename;
if(real_value.type && real_value.type !== "input")
value += ` [${real_value.type}]`;
}
return value;
}
});
async function uploadFile(file, updateNode, pasted = false) {
try {
// Wrap file in formdata so it includes filename
const body = new FormData();
body.append("image", file);
if (pasted) body.append("subfolder", "pasted");
const resp = await api.fetchApi("/upload/image", {
method: "POST",
body,
});
if (resp.status === 200) {
const data = await resp.json();
// Add the file to the dropdown list and update the widget value
let path = data.name;
if (data.subfolder) path = data.subfolder + "/" + path;
if (!audioWidget.options.values.includes(path)) {
audioWidget.options.values.push(path);
}
if (updateNode) {
audioWidget.value = path;
// showAudio(path)
playAudio(node, path);
}
} else {
alert(resp.status + " - " + resp.statusText);
}
} catch (error) {
alert(error);
}
}
const fileInput = document.createElement("input");
Object.assign(fileInput, {
type: "file",
accept: "audio/mp3,audio/wav,audio/flac,audio/m4a",
style: "display: none",
onchange: async () => {
if (fileInput.files.length) {
await uploadFile(fileInput.files[0], true);
}
},
});
document.body.append(fileInput);
// Create the button widget for selecting the files
uploadWidget = node.addWidget("button", "choose audio file to upload", "Audio", () => {
fileInput.click();
});
uploadWidget.serialize = false;
playAudio(node, audioWidget.value);
const cb = node.callback;
audioWidget.callback = function () {
playAudio(node, audioWidget.value);
if (cb) {
return cb.apply(this, arguments);
}
};
return { widget: uploadWidget };
}
ComfyWidgets.AUDIOUPLOAD = audioUpload;
app.registerExtension({
name: "UVR5.UploadAudio",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData?.name == "LoadAudio") {
nodeData.input.required.upload = ["AUDIOUPLOAD"];
}
},
});
-85
View File
@@ -1,85 +0,0 @@
/**
* File: playAudio.js
* Project: comfyui_jags_audiotools
* Author: jags111
*
* Copyright (c) 2023 jags111
*
*/
import { app } from "../../../scripts/app.js";
import { api } from '../../../scripts/api.js'
import { ComfyWidgets } from "../../../scripts/widgets.js"
/*
A method that returns the required style for the html
*/
function addPlaybackWidget(node, name, root) {
try {
while (node.widgets.length > 0){
node.widgets.pop()
}
} catch (error) {
//console.log(error);
}
let url = `/view?filename=${encodeURIComponent(name)}&type=${root}&subfolder=${app.getPreviewFormatParam()}${app.getRandParam()}`;
let isTick = true;
const audio = new Audio(url);
const slider = node.addWidget(
"slider",
"loading",
0,
(v) => {
if (!isTick) {
audio.currentTime = v;
}
isTick = false;
},
{
min: 0,
max: 0,
}
);
const button = node.addWidget("button", `Play ${name}`, "play", () => {
try {
if (audio.paused) {
audio.play();
button.name = `Pause ${name}`;
} else {
audio.pause();
button.name = `Play ${name}`;
}
} catch (error) {
alert(error);
}
app.canvas.setDirty(true);
});
audio.addEventListener("timeupdate", () => {
isTick = true;
slider.value = audio.currentTime;
app.canvas.setDirty(true);
});
audio.addEventListener("ended", () => {
button.name = `Play ${name}`;
app.canvas.setDirty(true);
});
audio.addEventListener("loadedmetadata", () => {
slider.options.max = audio.duration;
slider.name = `(${audio.duration})`;
app.canvas.setDirty(true);
});
}
app.registerExtension({
name: "UVR5.AudioPlayer",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData?.name == "PlayAudio") {
nodeType.prototype.onExecuted = function (data) {
addPlaybackWidget(this, data.audio[0], data.audio[1]);
}
}
}
});
+155
View File
@@ -0,0 +1,155 @@
import { app } from "../../../scripts/app.js";
import { api } from '../../../scripts/api.js'
function fitHeight(node) {
node.setSize([node.size[0], node.computeSize([node.size[0], node.size[1]])[1]])
node?.graph?.setDirtyCanvas(true);
}
function chainCallback(object, property, callback) {
if (object == undefined) {
//This should not happen.
console.error("Tried to add callback to non-existant object")
return;
}
if (property in object) {
const callback_orig = object[property]
object[property] = function () {
const r = callback_orig.apply(this, arguments);
callback.apply(this, arguments);
return r
};
} else {
object[property] = callback;
}
}
function addPreviewOptions(nodeType) {
chainCallback(nodeType.prototype, "getExtraMenuOptions", function(_, options) {
// The intended way of appending options is returning a list of extra options,
// but this isn't used in widgetInputs.js and would require
// less generalization of chainCallback
let optNew = []
try {
const previewWidget = this.widgets.find((w) => w.name === "audiopreview");
let url = null
if (previewWidget.audioEl?.hidden == false && previewWidget.audioEl.src) {
//Use full quality audio
//url = api.apiURL('/view?' + new URLSearchParams(previewWidget.value.params));
url = previewWidget.audioEl.src
}
if (url) {
optNew.push(
{
content: "Open preview",
callback: () => {
window.open(url, "_blank")
},
},
{
content: "Save preview",
callback: () => {
const a = document.createElement("a");
a.href = url;
a.setAttribute("download", new URLSearchParams(previewWidget.value.params).get("filename"));
document.body.append(a);
a.click();
requestAnimationFrame(() => a.remove());
},
}
);
}
if(options.length > 0 && options[0] != null && optNew.length > 0) {
optNew.push(null);
}
options.unshift(...optNew);
} catch (error) {
console.log(error);
}
});
}
function previewAudio(node,file,type){
var element = document.createElement("div");
const previewNode = node;
var previewWidget = node.addDOMWidget("audiopreview", "preview", element, {
serialize: false,
hideOnZoom: false,
getValue() {
return element.value;
},
setValue(v) {
element.value = v;
},
});
previewWidget.computeSize = function(width) {
if (this.aspectRatio && !this.parentEl.hidden) {
let height = (previewNode.size[0]-20)/ this.aspectRatio + 10;
if (!(height > 0)) {
height = 0;
}
this.computedHeight = height + 10;
return [width, height];
}
return [width, -4];//no loaded src, widget should not display
}
// element.style['pointer-events'] = "none"
previewWidget.value = {hidden: false, paused: false, params: {}}
previewWidget.parentEl = document.createElement("div");
previewWidget.parentEl.className = "audio_preview";
previewWidget.parentEl.style['width'] = "100%"
element.appendChild(previewWidget.parentEl);
previewWidget.audioEl = document.createElement("audio");
previewWidget.audioEl.controls = true;
previewWidget.audioEl.loop = false;
previewWidget.audioEl.muted = false;
previewWidget.audioEl.style['width'] = "100%"
previewWidget.audioEl.addEventListener("loadedmetadata", () => {
previewWidget.aspectRatio = previewWidget.audioEl.audioWidth / previewWidget.audioEl.audioHeight;
fitHeight(this);
});
previewWidget.audioEl.addEventListener("error", () => {
//TODO: consider a way to properly notify the user why a preview isn't shown.
previewWidget.parentEl.hidden = true;
fitHeight(this);
});
let params = {
"filename": file,
"type": type,
}
previewWidget.parentEl.hidden = previewWidget.value.hidden;
previewWidget.audioEl.autoplay = !previewWidget.value.paused && !previewWidget.value.hidden;
let target_width = 256
if (element.style?.width) {
//overscale to allow scrolling. Endpoint won't return higher than native
target_width = element.style.width.slice(0,-2)*2;
}
if (!params.force_size || params.force_size.includes("?") || params.force_size == "Disabled") {
params.force_size = target_width+"x?"
} else {
let size = params.force_size.split("x")
let ar = parseInt(size[0])/parseInt(size[1])
params.force_size = target_width+"x"+(target_width/ar)
}
previewWidget.audioEl.src = api.apiURL('/view?' + new URLSearchParams(params));
previewWidget.audioEl.hidden = false;
previewWidget.parentEl.appendChild(previewWidget.audioEl)
}
app.registerExtension({
name: "UVR5.AudioPreviewer",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData?.name == "PreViewAudio") {
nodeType.prototype.onExecuted = function (data) {
previewAudio(this, data.audio[0], data.audio[1]);
}
addPreviewOptions(nodeType)
}
}
});
+203
View File
@@ -0,0 +1,203 @@
import { app } from "../../../scripts/app.js";
import { api } from '../../../scripts/api.js'
import { ComfyWidgets } from "../../../scripts/widgets.js"
function fitHeight(node) {
node.setSize([node.size[0], node.computeSize([node.size[0], node.size[1]])[1]])
node?.graph?.setDirtyCanvas(true);
}
function previewAudio(node,file){
while (node.widgets.length > 2){
node.widgets.pop();
}
try {
var el = document.getElementById("uploadAudio");
el.remove();
} catch (error) {
console.log(error);
}
var element = document.createElement("div");
element.id = "uploadAudio";
const previewNode = node;
var previewWidget = node.addDOMWidget("audiopreview", "preview", element, {
serialize: false,
hideOnZoom: false,
getValue() {
return element.value;
},
setValue(v) {
element.value = v;
},
});
previewWidget.computeSize = function(width) {
if (this.aspectRatio && !this.parentEl.hidden) {
let height = (previewNode.size[0]-20)/ this.aspectRatio + 10;
if (!(height > 0)) {
height = 0;
}
this.computedHeight = height + 10;
return [width, height];
}
return [width, -4];//no loaded src, widget should not display
}
// element.style['pointer-events'] = "none"
previewWidget.value = {hidden: false, paused: false, params: {}}
previewWidget.parentEl = document.createElement("div");
previewWidget.parentEl.className = "audio_preview";
previewWidget.parentEl.style['width'] = "100%"
element.appendChild(previewWidget.parentEl);
previewWidget.audioEl = document.createElement("audio");
previewWidget.audioEl.controls = true;
previewWidget.audioEl.loop = false;
previewWidget.audioEl.muted = false;
previewWidget.audioEl.style['width'] = "100%"
previewWidget.audioEl.addEventListener("loadedmetadata", () => {
previewWidget.aspectRatio = previewWidget.audioEl.audioWidth / previewWidget.audioEl.audioHeight;
fitHeight(this);
});
previewWidget.audioEl.addEventListener("error", () => {
//TODO: consider a way to properly notify the user why a preview isn't shown.
previewWidget.parentEl.hidden = true;
fitHeight(this);
});
let params = {
"filename": file,
"type": "input",
}
previewWidget.parentEl.hidden = previewWidget.value.hidden;
previewWidget.audioEl.autoplay = !previewWidget.value.paused && !previewWidget.value.hidden;
let target_width = 256
if (element.style?.width) {
//overscale to allow scrolling. Endpoint won't return higher than native
target_width = element.style.width.slice(0,-2)*2;
}
if (!params.force_size || params.force_size.includes("?") || params.force_size == "Disabled") {
params.force_size = target_width+"x?"
} else {
let size = params.force_size.split("x")
let ar = parseInt(size[0])/parseInt(size[1])
params.force_size = target_width+"x"+(target_width/ar)
}
previewWidget.audioEl.src = api.apiURL('/view?' + new URLSearchParams(params));
previewWidget.audioEl.hidden = false;
previewWidget.parentEl.appendChild(previewWidget.audioEl)
}
function audioUpload(node, inputName, inputData, app) {
const audioWidget = node.widgets.find((w) => w.name === "audio");
let uploadWidget;
/*
A method that returns the required style for the html
*/
var default_value = audioWidget.value;
Object.defineProperty(audioWidget, "value", {
set : function(value) {
this._real_value = value;
},
get : function() {
let value = "";
if (this._real_value) {
value = this._real_value;
} else {
return default_value;
}
if (value.filename) {
let real_value = value;
value = "";
if (real_value.subfolder) {
value = real_value.subfolder + "/";
}
value += real_value.filename;
if(real_value.type && real_value.type !== "input")
value += ` [${real_value.type}]`;
}
return value;
}
});
async function uploadFile(file, updateNode, pasted = false) {
try {
// Wrap file in formdata so it includes filename
const body = new FormData();
body.append("image", file);
if (pasted) body.append("subfolder", "pasted");
const resp = await api.fetchApi("/upload/image", {
method: "POST",
body,
});
if (resp.status === 200) {
const data = await resp.json();
// Add the file to the dropdown list and update the widget value
let path = data.name;
if (data.subfolder) path = data.subfolder + "/" + path;
if (!audioWidget.options.values.includes(path)) {
audioWidget.options.values.push(path);
}
if (updateNode) {
audioWidget.value = path;
previewAudio(node,path)
}
} else {
alert(resp.status + " - " + resp.statusText);
}
} catch (error) {
alert(error);
}
}
const fileInput = document.createElement("input");
Object.assign(fileInput, {
type: "file",
accept: "audio/mp3,audio/wav,audio/flac,audio/m4a",
style: "display: none",
onchange: async () => {
if (fileInput.files.length) {
await uploadFile(fileInput.files[0], true);
}
},
});
document.body.append(fileInput);
// Create the button widget for selecting the files
uploadWidget = node.addWidget("button", "choose audio file to upload", "Audio", () => {
fileInput.click();
});
uploadWidget.serialize = false;
previewAudio(node, audioWidget.value);
const cb = node.callback;
audioWidget.callback = function () {
previewAudio(node,audioWidget.value);
if (cb) {
return cb.apply(this, arguments);
}
};
return { widget: uploadWidget };
}
ComfyWidgets.AUDIOPLOAD = audioUpload;
app.registerExtension({
name: "UVR5.UploadAudio",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData?.name == "LoadAudio") {
nodeData.input.required.upload = ["AUDIOPLOAD"];
}
},
});