import { app } from "../../scripts/app.js" import { api } from "../../scripts/api.js" function splitFilePath(path) { const folder_separator = path.lastIndexOf("/") if (folder_separator === -1) { return ["", path] } return [ path.substring(0, folder_separator), path.substring(folder_separator + 1) ] } function getResourceURL(subfolder, filename, type = "input") { const params = [ "filename=" + encodeURIComponent(filename), "type=" + type, "subfolder=" + subfolder, app.getRandParam().substring(1) ].join("&") return `/view?${params}` } async function uploadFile( audioWidget, audioUIWidget, 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) { audioUIWidget.element.src = api.apiURL( getResourceURL(...splitFilePath(path)) ) audioWidget.value = path } } else { alert(resp.status + " - " + resp.statusText) } } catch (error) { alert(error) } } // AudioWidget MUST be registered first, as AUDIOUPLOAD depends on AUDIO_UI to be // present. app.registerExtension({ name: "Comfy.AudioWidget", async beforeRegisterNodeDef(nodeType, nodeData) { if (["LoadAudio", "SaveAudio", "PreviewAudio"].includes(nodeType.comfyClass)) { nodeData.input.required.audioUI = ["AUDIO_UI"] } }, getCustomWidgets() { return { AUDIO_UI(node, inputName) { const audio = document.createElement("audio") audio.controls = true audio.classList.add("comfy-audio") audio.setAttribute("name", "media") const audioUIWidget = node.addDOMWidget( inputName, /* name=*/ "audioUI", audio ) // @ts-ignore // TODO: Sort out the DOMWidget type. audioUIWidget.serialize = false const isOutputNode = node.constructor.nodeData.output_node if (isOutputNode) { // Hide the audio widget when there is no audio initially. audioUIWidget.element.classList.add("empty-audio-widget") // Populate the audio widget UI on node execution. const onExecuted = node.onExecuted node.onExecuted = function(message) { onExecuted?.apply(this, arguments) const audios = message.audio if (!audios) return const audio = audios[0] audioUIWidget.element.src = api.apiURL( getResourceURL(audio.subfolder, audio.filename, audio.type) ) audioUIWidget.element.classList.remove("empty-audio-widget") } } return { widget: audioUIWidget } } } }, onNodeOutputsUpdated(nodeOutputs) { for (const [nodeId, output] of Object.entries(nodeOutputs)) { const node = app.graph.getNodeById(Number.parseInt(nodeId)); if ("audio" in output) { const audioUIWidget = node.widgets.find((w) => w.name === "audioUI"); const audio = output.audio[0]; audioUIWidget.element.src = api.apiURL(getResourceURL(audio.subfolder, audio.filename, audio.type)); audioUIWidget.element.classList.remove("empty-audio-widget"); } } }, }) app.registerExtension({ name: "Comfy.UploadAudio", async beforeRegisterNodeDef(nodeType, nodeData) { if (nodeData?.input?.required?.audio?.[1]?.audio_upload === true) { nodeData.input.required.upload = ["AUDIOUPLOAD"] } }, getCustomWidgets() { return { AUDIOUPLOAD(node, inputName) { // The widget that allows user to select file. const audioWidget = node.widgets.find(w => w.name === "audio") const audioUIWidget = node.widgets.find(w => w.name === "audioUI") const onAudioWidgetUpdate = () => { audioUIWidget.element.src = api.apiURL( getResourceURL(...splitFilePath(audioWidget.value)) ) } // Initially load default audio file to audioUIWidget. if (audioWidget.value) { onAudioWidgetUpdate() } audioWidget.callback = onAudioWidgetUpdate // Load saved audio file widget values if restoring from workflow const onGraphConfigured = node.onGraphConfigured; node.onGraphConfigured = function() { onGraphConfigured?.apply(this, arguments) if (audioWidget.value) { onAudioWidgetUpdate() } } const fileInput = document.createElement("input") fileInput.type = "file" fileInput.accept = "audio/*" fileInput.style.display = "none" fileInput.onchange = () => { if (fileInput.files.length) { uploadFile(audioWidget, audioUIWidget, fileInput.files[0], true) } } // The widget to pop up the upload dialog. const uploadWidget = node.addWidget( "button", inputName, /* value=*/ "", () => { fileInput.click() } ) uploadWidget.label = "choose file to upload" uploadWidget.serialize = false return { widget: uploadWidget } } } } })