forked from comfyanonymous/ComfyUI
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add audio widget (comfyanonymous#3863)
* Add audio widget * Fix audio bugs * Add CSS * Populate audio widget when load history
- Loading branch information
Showing
4 changed files
with
199 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
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.getPreviewFormatParam().substring(1), | ||
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"].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, "output") | ||
) | ||
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, "output")); | ||
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. | ||
onAudioWidgetUpdate() | ||
audioWidget.callback = 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 } | ||
} | ||
} | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -632,3 +632,7 @@ dialog::backdrop { | |
border-top: none; | ||
} | ||
} | ||
|
||
audio.comfy-audio.empty-audio-widget { | ||
display: none; | ||
} |