added preview widgets

This commit is contained in:
SayanoAI
2024-05-27 19:40:15 -04:00
parent 8e3053e482
commit 97f5e8b2b7
9 changed files with 1301 additions and 371 deletions
+3 -2
View File
@@ -241,11 +241,12 @@ class LoadAudio:
def load_audio(self, audio, sr):
audio_path = folder_paths.get_annotated_filepath(audio)
print(f"{audio_path=} {audio=}")
widgetId = get_hash(audio_path)
audio_name = os.path.basename(audio).split(".")[0]
sr = None if sr=="None" else int(sr)
audio = load_input_audio(audio_path,sr=sr)
return (audio_name,lambda:audio_to_bytes(*audio))
return {"ui": {"preview": [{"filename": audio_name, "type": "input", "widgetId": widgetId}]}, "result": (audio_name, lambda:audio_to_bytes(*audio))}
# return (audio_name,lambda:audio_to_bytes(*audio))
@classmethod
def IS_CHANGED(cls, audio):
+15 -2
View File
@@ -1,10 +1,13 @@
import os
import shutil
from ..lib.audio import SUPPORTED_AUDIO, audio_to_bytes, bytes_to_audio, load_input_audio, save_input_audio
from ..lib.utils import get_hash
from .utils import increment_filename_no_overwrite
import folder_paths
temp_path = folder_paths.get_temp_directory()
class PreviewAudio:
def __init__(self):
pass
@@ -18,6 +21,7 @@ class PreviewAudio:
"save_format": (SUPPORTED_AUDIO,{"default": "flac"},),
"save_channels": ([1,2],{"default": 1}),
"overwrite_existing": ("BOOLEAN", {"default": True}),
"autoplay": ("BOOLEAN", {"default": True}),
},
}
@@ -30,7 +34,7 @@ class PreviewAudio:
FUNCTION = "save_audio"
def save_audio(self, audio, filename, save_format, save_channels, overwrite_existing):
def save_audio(self, audio, filename, save_format, save_channels, overwrite_existing, autoplay):
filename = filename.strip()
assert filename, "Filename cannot be empty"
@@ -44,10 +48,19 @@ class PreviewAudio:
if os.path.isfile(output_path) and not overwrite_existing:
output_path = increment_filename_no_overwrite(output_path)
input_audio = bytes_to_audio(audio())
print(save_input_audio(output_path,input_audio,to_int16=True,to_stereo=save_channels==2))
return output_path, lambda:audio_to_bytes(*input_audio)
tempdir = os.path.join(temp_path,"preview")
os.makedirs(tempdir, exist_ok=True)
widgetId = get_hash(output_path,save_channels)
audio_name = f"{widgetId}.{save_format}"
preview_file = os.path.join(tempdir,audio_name)
if not os.path.isfile(preview_file): shutil.copyfile(output_path,preview_file)
return {"ui": {"preview": [{
"filename": audio_name, "type": "temp", "subfolder": "preview", "widgetId": widgetId, "autoplay": autoplay
}]}, "result": (output_path, lambda:audio_to_bytes(*input_audio))}
@classmethod
def IS_CHANGED(cls, audio):
+9 -2
View File
@@ -1,4 +1,5 @@
import os
import shutil
from ..lib.audio import audio_to_bytes, bytes_to_audio, load_input_audio, save_input_audio
@@ -57,7 +58,8 @@ class RVCNode:
def convert(self, audio, model, hubert_model, pitch_extraction_params, format, use_cache=True, **kwargs):
cache_name = os.path.join(BASE_CACHE_DIR,"rvc",f"{get_hash(audio(), model, *pitch_extraction_params.items())}.{format}")
widgetId = get_hash(audio(), model, *pitch_extraction_params.items())
cache_name = os.path.join(BASE_CACHE_DIR,"rvc",f"{widgetId}.{format}")
if use_cache and os.path.isfile(cache_name): output_audio = load_input_audio(cache_name)
else:
@@ -65,7 +67,12 @@ class RVCNode:
output_audio = vc_single(hubert_model=hubert_model(),input_audio=input_audio,**model(),**pitch_extraction_params,**kwargs)
print(save_input_audio(cache_name, output_audio))
return (lambda:audio_to_bytes(*output_audio),)
tempdir = os.path.join(temp_path,"preview")
os.makedirs(tempdir, exist_ok=True)
audio_name = os.path.basename(cache_name)
preview_file = os.path.join(tempdir,audio_name)
if not os.path.isfile(preview_file): shutil.copyfile(cache_name,preview_file)
return {"ui": {"preview": [{"filename": audio_name, "type": "temp", "subfolder": "preview", "widgetId": widgetId}]}, "result": (lambda:audio_to_bytes(*output_audio),)}
@classmethod
+13 -5
View File
@@ -10,7 +10,7 @@ from ..lib.utils import gc_collect, get_hash
from .settings.downloader import RVC_DOWNLOAD_LINK, download_file
from ..lib import BASE_MODELS_DIR
import numpy as np
from ..lib.audio import MAX_INT16, audio_to_bytes, bytes_to_audio, merge_audio, remix_audio
from ..lib.audio import MAX_INT16, audio_to_bytes, bytes_to_audio, load_input_audio, merge_audio, remix_audio, save_input_audio
CATEGORY = "🌺RVC-Studio/utils"
temp_path = folder_paths.get_temp_directory()
@@ -357,10 +357,18 @@ class MergeAudioNode:
def merge(self, audio1, audio2, sr, merge_type, normalize):
if sr=="None": sr=None
input_audio1 = bytes_to_audio(audio1())
input_audio2 = bytes_to_audio(audio2())
merged_audio = merge_audio(input_audio1,input_audio2,sr=sr,to_int16=True,norm=normalize,merge_type=merge_type)
return (lambda: audio_to_bytes(*merged_audio),)
widgetId = get_hash(audio1(),audio2(),sr,merge_type,normalize)
audio_path = os.path.join(temp_path,"preview",f"{widgetId}.flac")
if os.path.isfile(audio_path): merged_audio = load_input_audio(audio_path, sr, norm=normalize)
else:
input_audio1 = bytes_to_audio(audio1())
input_audio2 = bytes_to_audio(audio2())
merged_audio = merge_audio(input_audio1,input_audio2,sr=sr,to_int16=True,norm=normalize,merge_type=merge_type)
print(save_input_audio(audio_path,merged_audio))
audio_name = os.path.basename(audio_path)
return {"ui": {"preview": [{"filename": audio_name, "type": "temp", "subfolder": "preview", "widgetId": widgetId}]}, "result": (lambda: audio_to_bytes(*merged_audio),)}
@classmethod
def IS_CHANGED(cls, *args, **kwargs):
+1 -1
View File
@@ -84,4 +84,4 @@ class UVR5Node:
@classmethod
def IS_CHANGED(cls, *args, **kwargs):
print(f"{args=} {kwargs=}")
return get_hash(*args, *kwargs.items())
return get_hash(args,kwargs)
+894
View File
@@ -0,0 +1,894 @@
{
"last_node_id": 39,
"last_link_id": 64,
"nodes": [
{
"id": 7,
"type": "UVR5Node",
"pos": [
269,
-736
],
"size": {
"0": 315,
"1": 170
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "audio",
"type": "VHS_AUDIO",
"link": 20
}
],
"outputs": [
{
"name": "primary_stem",
"type": "VHS_AUDIO",
"links": [
49
],
"shape": 3,
"slot_index": 0
},
{
"name": "secondary_stem",
"type": "VHS_AUDIO",
"links": [
51
],
"shape": 3,
"slot_index": 1
},
{
"name": "audio_passthrough",
"type": "VHS_AUDIO",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "UVR5Node"
},
"widgets_values": [
"karafan/MDX23C-8KFFT-InstVoc_HQ.ckpt",
10,
"flac",
true
]
},
{
"id": 14,
"type": "UVR5Node",
"pos": [
277,
-505
],
"size": {
"0": 315,
"1": 170
},
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "audio",
"type": "VHS_AUDIO",
"link": 24
}
],
"outputs": [
{
"name": "primary_stem",
"type": "VHS_AUDIO",
"links": [
50
],
"shape": 3,
"slot_index": 0
},
{
"name": "secondary_stem",
"type": "VHS_AUDIO",
"links": [
52
],
"shape": 3,
"slot_index": 1
},
{
"name": "audio_passthrough",
"type": "VHS_AUDIO",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "UVR5Node"
},
"widgets_values": [
"UVR/HP5-vocals+instrumentals.pth",
10,
"flac",
true
]
},
{
"id": 17,
"type": "UVR5Node",
"pos": [
272.40658221699283,
-161.99581170557863
],
"size": {
"0": 315,
"1": 170
},
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "audio",
"type": "VHS_AUDIO",
"link": 53
}
],
"outputs": [
{
"name": "primary_stem",
"type": "VHS_AUDIO",
"links": [
56
],
"shape": 3,
"slot_index": 0
},
{
"name": "secondary_stem",
"type": "VHS_AUDIO",
"links": [],
"shape": 3,
"slot_index": 1
},
{
"name": "audio_passthrough",
"type": "VHS_AUDIO",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "UVR5Node"
},
"widgets_values": [
"UVR/5_HP-Karaoke-UVR.pth",
10,
"flac",
true
]
},
{
"id": 5,
"type": "LoadHubertModel",
"pos": [
306,
51
],
"size": {
"0": 315,
"1": 58
},
"flags": {
"collapsed": true
},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "hubert_model",
"type": "HUBERT_MODEL",
"links": [
58
],
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadHubertModel"
},
"widgets_values": [
"hubert_base.pt"
]
},
{
"id": 6,
"type": "LoadPitchExtractionParams",
"pos": [
288,
105
],
"size": {
"0": 315,
"1": 178
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "pitch_extraction_params",
"type": "PITCH_EXTRACTION",
"links": [
59
],
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadPitchExtractionParams"
},
"widgets_values": [
"rmvpe+",
false,
0.75,
0,
0.25,
0.25
]
},
{
"id": 4,
"type": "LoadRVCModelNode",
"pos": [
649,
-136
],
"size": {
"0": 315,
"1": 78
},
"flags": {},
"order": 2,
"mode": 0,
"outputs": [
{
"name": "model",
"type": "RVC_MODEL",
"links": [
57
],
"shape": 3
},
{
"name": "model_name",
"type": "STRING",
"links": [
40
],
"shape": 3,
"slot_index": 1
}
],
"properties": {
"Node name for S&R": "LoadRVCModelNode"
},
"widgets_values": [
"RVC/loss27_Sayano_e151.pth"
]
},
{
"id": 35,
"type": "MergeAudioNode",
"pos": [
666,
-728
],
"size": [
315,
126
],
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "audio1",
"type": "VHS_AUDIO",
"link": 49
},
{
"name": "audio2",
"type": "VHS_AUDIO",
"link": 50
}
],
"outputs": [
{
"name": "vhs_audio",
"type": "VHS_AUDIO",
"links": [
53
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "MergeAudioNode"
},
"widgets_values": [
"None",
"min",
true,
{
"hidden": false,
"paused": false,
"params": {}
}
]
},
{
"id": 36,
"type": "MergeAudioNode",
"pos": [
676,
-528
],
"size": {
"0": 315,
"1": 126
},
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "audio1",
"type": "VHS_AUDIO",
"link": 51
},
{
"name": "audio2",
"type": "VHS_AUDIO",
"link": 52
}
],
"outputs": [
{
"name": "vhs_audio",
"type": "VHS_AUDIO",
"links": [
54
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "MergeAudioNode"
},
"widgets_values": [
"None",
"mean",
true
]
},
{
"id": 37,
"type": "RVCNode",
"pos": [
643,
5
],
"size": {
"0": 309.34271240234375,
"1": 166
},
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "audio",
"type": "VHS_AUDIO",
"link": 56
},
{
"name": "model",
"type": "RVC_MODEL",
"link": 57
},
{
"name": "hubert_model",
"type": "HUBERT_MODEL",
"link": 58
},
{
"name": "pitch_extraction_params",
"type": "PITCH_EXTRACTION",
"link": 59
}
],
"outputs": [
{
"name": "VHS_AUDIO",
"type": "VHS_AUDIO",
"links": [
60
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "RVCNode"
},
"widgets_values": [
0,
"flac",
true
]
},
{
"id": 33,
"type": "MergeAudioNode",
"pos": [
1154,
-367
],
"size": {
"0": 315,
"1": 126
},
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "audio1",
"type": "VHS_AUDIO",
"link": 54
},
{
"name": "audio2",
"type": "VHS_AUDIO",
"link": 60
}
],
"outputs": [
{
"name": "vhs_audio",
"type": "VHS_AUDIO",
"links": [
63
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "MergeAudioNode"
},
"widgets_values": [
"None",
"mean",
true
]
},
{
"id": 31,
"type": "JoinStrings",
"pos": [
726,
241
],
"size": {
"0": 315,
"1": 106
},
"flags": {
"collapsed": true
},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "string1",
"type": "STRING",
"link": 62,
"widget": {
"name": "string1"
}
},
{
"name": "string2",
"type": "STRING",
"link": 40,
"widget": {
"name": "string2"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
64
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "JoinStrings"
},
"widgets_values": [
"",
"",
"\\"
]
},
{
"id": 39,
"type": "PreviewAudio",
"pos": [
1514,
-358
],
"size": {
"0": 315,
"1": 150
},
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "audio",
"type": "VHS_AUDIO",
"link": 63
},
{
"name": "filename",
"type": "STRING",
"link": 64,
"widget": {
"name": "filename"
},
"slot_index": 1
}
],
"outputs": [
{
"name": "output_path",
"type": "STRING",
"links": null,
"shape": 3
},
{
"name": "vhs_audio",
"type": "VHS_AUDIO",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "PreviewAudio"
},
"widgets_values": [
"test",
"flac",
2,
true,
true
]
},
{
"id": 38,
"type": "LoadAudio",
"pos": [
-171,
-739
],
"size": [
309.89001187135455,
126
],
"flags": {},
"order": 3,
"mode": 0,
"outputs": [
{
"name": "audio_name",
"type": "STRING",
"links": [
62
],
"shape": 3,
"slot_index": 0
},
{
"name": "vhs_audio",
"type": "VHS_AUDIO",
"links": [
61
],
"shape": 3,
"slot_index": 1
}
],
"properties": {
"Node name for S&R": "LoadAudio"
},
"widgets_values": [
"already-enough.mp3",
"None",
"image"
]
},
{
"id": 11,
"type": "UVR5Node",
"pos": [
-172,
-567
],
"size": {
"0": 315,
"1": 170
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "audio",
"type": "VHS_AUDIO",
"link": 61
}
],
"outputs": [
{
"name": "primary_stem",
"type": "VHS_AUDIO",
"links": null,
"shape": 3,
"slot_index": 0
},
{
"name": "secondary_stem",
"type": "VHS_AUDIO",
"links": [
20,
24
],
"shape": 3,
"slot_index": 1
},
{
"name": "audio_passthrough",
"type": "VHS_AUDIO",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "UVR5Node"
},
"widgets_values": [
"UVR/UVR-DeEcho-DeReverb.pth",
10,
"flac",
true
]
}
],
"links": [
[
20,
11,
1,
7,
0,
"VHS_AUDIO"
],
[
24,
11,
1,
14,
0,
"VHS_AUDIO"
],
[
40,
4,
1,
31,
1,
"STRING"
],
[
49,
7,
0,
35,
0,
"VHS_AUDIO"
],
[
50,
14,
0,
35,
1,
"VHS_AUDIO"
],
[
51,
7,
1,
36,
0,
"VHS_AUDIO"
],
[
52,
14,
1,
36,
1,
"VHS_AUDIO"
],
[
53,
35,
0,
17,
0,
"VHS_AUDIO"
],
[
54,
36,
0,
33,
0,
"VHS_AUDIO"
],
[
56,
17,
0,
37,
0,
"VHS_AUDIO"
],
[
57,
4,
0,
37,
1,
"RVC_MODEL"
],
[
58,
5,
0,
37,
2,
"HUBERT_MODEL"
],
[
59,
6,
0,
37,
3,
"PITCH_EXTRACTION"
],
[
60,
37,
0,
33,
1,
"VHS_AUDIO"
],
[
61,
38,
1,
11,
0,
"VHS_AUDIO"
],
[
62,
38,
0,
31,
0,
"STRING"
],
[
63,
33,
0,
39,
0,
"VHS_AUDIO"
],
[
64,
31,
0,
39,
1,
"STRING"
]
],
"groups": [
{
"title": "Preprocess",
"bounding": [
-181,
-813,
336,
507
],
"color": "#3f789e",
"font_size": 24
},
{
"title": "Ensemble Vocal",
"bounding": [
259,
-810,
742,
485
],
"color": "#3f789e",
"font_size": 24
},
{
"title": "Postprocess",
"bounding": [
262,
-236,
712,
529
],
"color": "#3f789e",
"font_size": 24
},
{
"title": "Output Audio",
"bounding": [
1144,
-441,
698,
255
],
"color": "#3f789e",
"font_size": 24
}
],
"config": {},
"extra": {
"ds": {
"scale": 0.5644739300537777,
"offset": [
-568.7175814310663,
890.3856832248159
]
}
},
"version": 0.4
}
-156
View File
@@ -1,156 +0,0 @@
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: "RVC-Studio.PreviewAudio",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData?.name == "PreviewAudio" && nodeData.category?.includes("RVC")) {
console.log({nodeData})
nodeType.prototype.onExecuted = function (data) {
previewAudio(this, data.audio[0], data.audio[1]);
console.log({data})
}
addPreviewOptions(nodeType)
}
}
});
+366
View File
@@ -0,0 +1,366 @@
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 addPreviewWidget(nodeType, nodeData, widgetName="audio", when="onNodeCreated") {
switch (when){
case "onNodeCreated":
// TODO: not working
// chainCallback(nodeType.prototype, "onNodeCreated", function () {
// const audioWidget = this.widgets.find((w) => w.name === widgetName);
// if (!audioWidget || !audioWidget?.value) return null
// const widgetId = `opt_widget-${Math.random()}`
// let options = {
// widgetId, type: "input", filename: audioWidget.value
// }
// nodeData.input.hidden = {
// ...nodeData.input.hidden,
// [widgetId]: ["AUDIOPREVIEW",options]
// };
// previewAudio(this,options)
// audioWidget.callback = function () {
// options = {
// widgetId, type: "input", filename: audioWidget.value
// }
// previewAudio(this,options)
// if (cb) {
// return cb.apply(this, arguments);
// }
// };
// })
break;
case "onExecuted":
chainCallback(nodeType.prototype, "onExecuted", function (data) {
const widgetId = `opt_widget-${Math.random()}`
nodeData.input.hidden = {
...nodeData.input.hidden,
[widgetId]: ["AUDIOPREVIEW",{widgetName,...data.preview[0]}]
};
previewAudio(this,data.preview[0])
})
break;
default: console.log({nodeType, nodeData, widgetName, when, node: this})
}
}
function previewAudio(node,options={filename: null, type: "input", widgetName: "audio", widgetId: null}){
// while (node.widgets.length > 2){
// node.widgets.pop();
// }
try {
console.log({node, options})
var el = document.getElementById(options.widgetId);
el?.remove();
} catch (error) {
console.log(error);
}
var element = document.createElement("div");
element.id = options.widgetId
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 (node.aspectRatio && !node.parentEl.hidden) {
let height = (previewNode.size[0]-20)/ node.aspectRatio + 10;
if (!(height > 0)) {
height = 0;
}
node.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(node);
});
previewWidget.audioEl.addEventListener("error", () => {
//TODO: consider a way to properly notify the user why a preview isn't shown.
previewWidget.parentEl.hidden = true;
fitHeight(node);
});
let params = options;
previewWidget.parentEl.hidden = previewWidget.value.hidden;
previewWidget.audioEl.autoplay = !previewWidget.value.paused && !previewWidget.value.hidden && Boolean(options.autoplay);
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)
return previewWidget
}
function audioPreview(node, inputName, inputData, app) {
const options = inputData.pop()
const audioWidget = node.widgets.find((w) => w.name === options.widgetName);
if (!audioWidget) return null
/*
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;
}
});
let element = previewAudio(node, {filename: audioWidget.value, ...options});
const cb = node.callback;
audioWidget.callback = function () {
element = previewAudio(node, {filename: audioWidget.value, ...options});
if (cb) {
return cb.apply(this, arguments);
}
};
// document.body.append(element);
// console.log({element})
// let uploadWidget = this.addWidget(element);
// uploadWidget.options.serialize = false;
return { widget: element };
}
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;
}
}
async function uploadFile(file) {
//TODO: Add uploaded file to cache with Cache.put()?
try {
// Wrap file in formdata so it includes filename
const body = new FormData();
const i = file.webkitRelativePath.lastIndexOf('/');
const subfolder = file.webkitRelativePath.slice(0,i+1)
const new_file = new File([file], file.name, {
type: file.type,
lastModified: file.lastModified,
});
body.append("image", new_file);
if (i > 0) {
body.append("subfolder", subfolder);
}
const resp = await api.fetchApi("/upload/image", {
method: "POST",
body,
});
if (resp.status === 200) {
return resp.status
} else {
alert(resp.status + " - " + resp.statusText);
}
} catch (error) {
alert(error);
}
}
function addUploadWidget(nodeType, nodeData, widgetName, type="video") {
chainCallback(nodeType.prototype, "onNodeCreated", function() {
const pathWidget = this.widgets.find((w) => w.name === widgetName);
const fileInput = document.createElement("input");
chainCallback(nodeType.prototype, "onRemoved", () => {
fileInput?.remove();
});
if (type == "folder") {
Object.assign(fileInput, {
type: "file",
style: "display: none",
webkitdirectory: true,
onchange: async () => {
const directory = fileInput.files[0].webkitRelativePath;
const i = directory.lastIndexOf('/');
if (i <= 0) {
throw "No directory found";
}
const path = directory.slice(0,directory.lastIndexOf('/'))
if (pathWidget.options.values.includes(path)) {
alert("A folder of the same name already exists");
return;
}
let successes = 0;
for(const file of fileInput.files) {
if (await uploadFile(file) == 200) {
successes++;
} else {
//Upload failed, but some prior uploads may have succeeded
//Stop future uploads to prevent cascading failures
//and only add to list if an upload has succeeded
if (successes > 0) {
break
} else {
return;
}
}
}
pathWidget.options.values.push(path);
pathWidget.value = path;
if (pathWidget.callback) {
pathWidget.callback(path)
}
},
});
} else if (type == "video") {
Object.assign(fileInput, {
type: "file",
accept: "video/webm,video/mp4,video/mkv,image/gif",
style: "display: none",
onchange: async () => {
if (fileInput.files.length) {
if (await uploadFile(fileInput.files[0]) != 200) {
//upload failed and file can not be added to options
return;
}
const filename = fileInput.files[0].name;
pathWidget.options.values.push(filename);
pathWidget.value = filename;
if (pathWidget.callback) {
pathWidget.callback(filename)
}
}
},
});
} else if (type == "audio") {
Object.assign(fileInput, {
type: "file",
accept: "audio/mpeg,audio/wav,audio/x-wav,audio/ogg",
style: "display: none",
onchange: async () => {
if (fileInput.files.length) {
if (await uploadFile(fileInput.files[0]) != 200) {
//upload failed and file can not be added to options
return;
}
const filename = fileInput.files[0].name;
pathWidget.options.values.push(filename);
pathWidget.value = filename;
if (pathWidget.callback) {
pathWidget.callback(filename)
}
previewAudio(this, {filename, type: "input", widgetId: filename})
}
},
});
}else {
throw "Unknown upload type"
}
document.body.append(fileInput);
let uploadWidget = this.addWidget("button", "choose " + type + " to upload", "image", () => {
//clear the active click event
app.canvas.node_widget = null
fileInput.click();
});
uploadWidget.options.serialize = false;
});
}
// ComfyWidgets.AUDIOPREVIEW = audioPreview;
app.registerExtension({
name: "RVC-Studio.UI",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.category?.includes("RVC")){
switch (nodeData?.name){
case "LoadAudio":
addUploadWidget(nodeType, nodeData, "audio", "audio")
addPreviewWidget(nodeType, nodeData, "audio", "onNodeCreated" )
break
case "PreviewAudio":
addPreviewWidget(nodeType, nodeData, "audio", "onExecuted" )
break
case "MergeAudioNode":
addPreviewWidget(nodeType, nodeData, "audio", "onExecuted" )
break;
case "RVCNode":
addPreviewWidget(nodeType, nodeData, "audio", "onExecuted" )
break;
default:
break
}
}
},
});
-203
View File
@@ -1,203 +0,0 @@
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(node);
});
previewWidget.audioEl.addEventListener("error", () => {
//TODO: consider a way to properly notify the user why a preview isn't shown.
previewWidget.parentEl.hidden = true;
fitHeight(node);
});
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: "RVC-Studio.LoadAudio",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData?.name == "LoadAudio" && nodeData.category?.includes("RVC")) {
console.log({nodeData})
nodeData.input.required.upload = ["AUDIOPLOAD"];
}
},
});