Spaces:
Configuration error
Configuration error
| 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 } | |
| } | |
| } | |
| } | |
| }) | |