AudioManager: Auto-Refresh nach jeder Generierung
Dateiliste wird jetzt per API (/audiocraft/list) geladen statt über Node-Execution. Aktualisiert sich automatisch nach jeder Generierung. Refresh-Button für manuelles Update. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b90b816063
commit
8743133c43
2 changed files with 136 additions and 80 deletions
|
|
@ -27,4 +27,25 @@ async def delete_audio_file(request):
|
||||||
|
|
||||||
return web.json_response({"ok": True, "deleted": filename})
|
return web.json_response({"ok": True, "deleted": filename})
|
||||||
|
|
||||||
|
|
||||||
|
# API-Endpoint zum Auflisten aller Audio-Dateien
|
||||||
|
@server.PromptServer.instance.routes.get("/audiocraft/list")
|
||||||
|
async def list_audio_files(request):
|
||||||
|
audio_dir = "/app/ComfyUI/output/audio"
|
||||||
|
if not os.path.exists(audio_dir):
|
||||||
|
return web.json_response({"files": [], "summary": "Kein Audio-Ordner"})
|
||||||
|
|
||||||
|
files = []
|
||||||
|
total_size = 0
|
||||||
|
for f in sorted(os.listdir(audio_dir)):
|
||||||
|
if f.endswith(".wav"):
|
||||||
|
fpath = os.path.join(audio_dir, f)
|
||||||
|
size = os.path.getsize(fpath)
|
||||||
|
total_size += size
|
||||||
|
files.append({"name": f, "size_mb": round(size / 1024 / 1024, 2)})
|
||||||
|
|
||||||
|
summary = f"{len(files)} Dateien | {total_size / 1024 / 1024:.1f} MB"
|
||||||
|
return web.json_response({"files": files, "summary": summary})
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
|
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
|
||||||
|
|
|
||||||
|
|
@ -2,45 +2,6 @@ import { app } from "../../../scripts/app.js";
|
||||||
import { api } from "../../../scripts/api.js";
|
import { api } from "../../../scripts/api.js";
|
||||||
|
|
||||||
let loopEnabled = false;
|
let loopEnabled = false;
|
||||||
|
|
||||||
app.registerExtension({
|
|
||||||
name: "AudioCraft.AudioManager",
|
|
||||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
|
||||||
if (nodeData.name === "AudioManager") {
|
|
||||||
const onExecuted = nodeType.prototype.onExecuted;
|
|
||||||
nodeType.prototype.onExecuted = function (message) {
|
|
||||||
onExecuted?.apply(this, arguments);
|
|
||||||
|
|
||||||
if (!message?.text) return;
|
|
||||||
|
|
||||||
const summary = message.text[0]?.content || "";
|
|
||||||
const files = message.files || [];
|
|
||||||
|
|
||||||
if (this._managerWidget) {
|
|
||||||
this._managerContainer.innerHTML = "";
|
|
||||||
buildContent(this._managerContainer, summary, files);
|
|
||||||
} else {
|
|
||||||
const container = document.createElement("div");
|
|
||||||
container.style.cssText = "padding:4px;overflow-y:auto;";
|
|
||||||
buildContent(container, summary, files);
|
|
||||||
|
|
||||||
this._managerContainer = container;
|
|
||||||
this._managerWidget = this.addDOMWidget("audio_manager", "dom", container, {
|
|
||||||
serialize: false,
|
|
||||||
hideOnZoom: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const listHeight = Math.min(40 + files.length * 26, 800);
|
|
||||||
this._managerWidget.computeSize = () => [this.size[0], listHeight];
|
|
||||||
this.setSize([Math.max(this.size[0], 500), listHeight + 80]);
|
|
||||||
app.graph.setDirtyCanvas(true);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Track currently playing audio globally so only one plays at a time
|
|
||||||
let activeAudio = null;
|
let activeAudio = null;
|
||||||
let activeBtn = null;
|
let activeBtn = null;
|
||||||
|
|
||||||
|
|
@ -56,30 +17,107 @@ function stopActive() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function refreshFileList(node) {
|
||||||
|
try {
|
||||||
|
const resp = await api.fetchApi("/audiocraft/list");
|
||||||
|
const data = await resp.json();
|
||||||
|
const container = node._managerContainer;
|
||||||
|
container.innerHTML = "";
|
||||||
|
buildContent(container, data.summary, data.files);
|
||||||
|
|
||||||
|
const listHeight = Math.min(40 + data.files.length * 26, 800);
|
||||||
|
node._managerWidget.computeSize = () => [node.size[0], listHeight];
|
||||||
|
node.setSize([Math.max(node.size[0], 500), listHeight + 80]);
|
||||||
|
app.graph.setDirtyCanvas(true);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Refresh failed:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.registerExtension({
|
||||||
|
name: "AudioCraft.AudioManager",
|
||||||
|
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||||
|
if (nodeData.name === "AudioManager") {
|
||||||
|
const origOnNodeCreated = nodeType.prototype.onNodeCreated;
|
||||||
|
nodeType.prototype.onNodeCreated = function () {
|
||||||
|
origOnNodeCreated?.apply(this, arguments);
|
||||||
|
|
||||||
|
const container = document.createElement("div");
|
||||||
|
container.style.cssText = "padding:4px;overflow-y:auto;";
|
||||||
|
|
||||||
|
const loadingMsg = document.createElement("div");
|
||||||
|
loadingMsg.textContent = "Klicke \u21BB Refresh um Dateien zu laden";
|
||||||
|
loadingMsg.style.cssText = "color:#888;font-size:12px;padding:8px;";
|
||||||
|
container.appendChild(loadingMsg);
|
||||||
|
|
||||||
|
this._managerContainer = container;
|
||||||
|
this._managerWidget = this.addDOMWidget("audio_manager", "dom", container, {
|
||||||
|
serialize: false,
|
||||||
|
hideOnZoom: false,
|
||||||
|
});
|
||||||
|
this._managerWidget.computeSize = () => [this.size[0], 60];
|
||||||
|
|
||||||
|
// Auto-refresh on creation
|
||||||
|
setTimeout(() => refreshFileList(this), 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Also refresh when node is executed via Queue
|
||||||
|
const onExecuted = nodeType.prototype.onExecuted;
|
||||||
|
nodeType.prototype.onExecuted = function (message) {
|
||||||
|
onExecuted?.apply(this, arguments);
|
||||||
|
refreshFileList(this);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for any prompt execution to auto-refresh AudioManager nodes
|
||||||
|
api.addEventListener("executed", (event) => {
|
||||||
|
// After any node executes, refresh all AudioManager nodes
|
||||||
|
if (event?.detail?.node) {
|
||||||
|
const nodes = app.graph._nodes.filter(n => n.type === "AudioManager");
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node._managerContainer) {
|
||||||
|
refreshFileList(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function buildContent(container, summary, files) {
|
function buildContent(container, summary, files) {
|
||||||
// Header with summary + loop toggle
|
|
||||||
const headerRow = document.createElement("div");
|
const headerRow = document.createElement("div");
|
||||||
headerRow.style.cssText = "display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;padding:4px;background:#333;border-radius:4px;";
|
headerRow.style.cssText = "display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;padding:4px;background:#333;border-radius:4px;gap:4px;";
|
||||||
|
|
||||||
const headerText = document.createElement("span");
|
const headerText = document.createElement("span");
|
||||||
headerText.textContent = summary;
|
headerText.textContent = summary;
|
||||||
headerText.style.cssText = "font-weight:bold;color:#fff;font-size:13px;";
|
headerText.style.cssText = "font-weight:bold;color:#fff;font-size:12px;white-space:nowrap;";
|
||||||
|
|
||||||
const loopBtn = document.createElement("button");
|
const btnGroup = document.createElement("div");
|
||||||
loopBtn.textContent = loopEnabled ? "\uD83D\uDD01 Loop AN" : "\uD83D\uDD01 Loop AUS";
|
btnGroup.style.cssText = "display:flex;gap:4px;";
|
||||||
loopBtn.style.cssText = `background:${loopEnabled ? "#2a5a2a" : "#444"};border:none;color:${loopEnabled ? "#8f8" : "#888"};cursor:pointer;padding:3px 8px;border-radius:3px;font-size:11px;`;
|
|
||||||
loopBtn.onclick = () => {
|
const refreshBtn = document.createElement("button");
|
||||||
loopEnabled = !loopEnabled;
|
refreshBtn.textContent = "\u21BB Refresh";
|
||||||
loopBtn.textContent = loopEnabled ? "\uD83D\uDD01 Loop AN" : "\uD83D\uDD01 Loop AUS";
|
refreshBtn.style.cssText = "background:#2a3a5a;border:none;color:#8cf;cursor:pointer;padding:3px 8px;border-radius:3px;font-size:11px;white-space:nowrap;";
|
||||||
loopBtn.style.background = loopEnabled ? "#2a5a2a" : "#444";
|
refreshBtn.onclick = () => {
|
||||||
loopBtn.style.color = loopEnabled ? "#8f8" : "#888";
|
const node = app.graph._nodes.find(n => n._managerContainer === container.parentElement || n._managerContainer === container);
|
||||||
if (activeAudio) {
|
if (node) refreshFileList(node);
|
||||||
activeAudio.loop = loopEnabled;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loopBtn = document.createElement("button");
|
||||||
|
loopBtn.textContent = loopEnabled ? "\uD83D\uDD01 Loop AN" : "\uD83D\uDD01 Loop";
|
||||||
|
loopBtn.style.cssText = `background:${loopEnabled ? "#2a5a2a" : "#444"};border:none;color:${loopEnabled ? "#8f8" : "#888"};cursor:pointer;padding:3px 8px;border-radius:3px;font-size:11px;white-space:nowrap;`;
|
||||||
|
loopBtn.onclick = () => {
|
||||||
|
loopEnabled = !loopEnabled;
|
||||||
|
loopBtn.textContent = loopEnabled ? "\uD83D\uDD01 Loop AN" : "\uD83D\uDD01 Loop";
|
||||||
|
loopBtn.style.background = loopEnabled ? "#2a5a2a" : "#444";
|
||||||
|
loopBtn.style.color = loopEnabled ? "#8f8" : "#888";
|
||||||
|
if (activeAudio) activeAudio.loop = loopEnabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
btnGroup.appendChild(refreshBtn);
|
||||||
|
btnGroup.appendChild(loopBtn);
|
||||||
headerRow.appendChild(headerText);
|
headerRow.appendChild(headerText);
|
||||||
headerRow.appendChild(loopBtn);
|
headerRow.appendChild(btnGroup);
|
||||||
container.appendChild(headerRow);
|
container.appendChild(headerRow);
|
||||||
|
|
||||||
if (files.length === 0) return;
|
if (files.length === 0) return;
|
||||||
|
|
@ -112,13 +150,34 @@ function buildContent(container, summary, files) {
|
||||||
dlBtn.title = "Download";
|
dlBtn.title = "Download";
|
||||||
dlBtn.style.cssText = "color:#8cf;text-decoration:none;font-size:13px;padding:0 4px;";
|
dlBtn.style.cssText = "color:#8cf;text-decoration:none;font-size:13px;padding:0 4px;";
|
||||||
|
|
||||||
|
const delBtn = document.createElement("button");
|
||||||
|
delBtn.textContent = "\u2716";
|
||||||
|
delBtn.title = "Loeschen";
|
||||||
|
delBtn.style.cssText = "background:#533;border:none;color:#f66;cursor:pointer;padding:2px 6px;border-radius:3px;font-size:11px;margin-left:4px;";
|
||||||
|
delBtn.onclick = async () => {
|
||||||
|
if (activeBtn === playBtn) stopActive();
|
||||||
|
try {
|
||||||
|
const resp = await api.fetchApi("/audiocraft/delete", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {"Content-Type": "application/json"},
|
||||||
|
body: JSON.stringify({filename: f.name}),
|
||||||
|
});
|
||||||
|
if (resp.ok) {
|
||||||
|
row.style.opacity = "0.3";
|
||||||
|
row.style.pointerEvents = "none";
|
||||||
|
delBtn.textContent = "\u2714";
|
||||||
|
delBtn.style.color = "#888";
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Delete failed:", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
playBtn.onclick = () => {
|
playBtn.onclick = () => {
|
||||||
// If clicking the same button that's playing, stop it
|
|
||||||
if (activeBtn === playBtn) {
|
if (activeBtn === playBtn) {
|
||||||
stopActive();
|
stopActive();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Stop any other playing audio first
|
|
||||||
stopActive();
|
stopActive();
|
||||||
|
|
||||||
const audio = new Audio(audioSrc);
|
const audio = new Audio(audioSrc);
|
||||||
|
|
@ -139,30 +198,6 @@ function buildContent(container, summary, files) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const delBtn = document.createElement("button");
|
|
||||||
delBtn.textContent = "\u2716";
|
|
||||||
delBtn.title = "Loeschen";
|
|
||||||
delBtn.style.cssText = "background:#533;border:none;color:#f66;cursor:pointer;padding:2px 6px;border-radius:3px;font-size:11px;margin-left:4px;";
|
|
||||||
delBtn.onclick = async () => {
|
|
||||||
// Stop if this file is playing
|
|
||||||
if (activeBtn === playBtn) stopActive();
|
|
||||||
try {
|
|
||||||
const resp = await api.fetchApi("/audiocraft/delete", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {"Content-Type": "application/json"},
|
|
||||||
body: JSON.stringify({filename: f.name}),
|
|
||||||
});
|
|
||||||
if (resp.ok) {
|
|
||||||
row.style.opacity = "0.3";
|
|
||||||
row.style.pointerEvents = "none";
|
|
||||||
delBtn.textContent = "\u2714";
|
|
||||||
delBtn.style.color = "#888";
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Delete failed:", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
row.appendChild(name);
|
row.appendChild(name);
|
||||||
row.appendChild(size);
|
row.appendChild(size);
|
||||||
row.appendChild(playBtn);
|
row.appendChild(playBtn);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue