From b5af85a8909698c7d8269c8cdbc6fc15804b53aa Mon Sep 17 00:00:00 2001 From: Andre Date: Sat, 21 Mar 2026 17:58:26 +0100 Subject: [PATCH] =?UTF-8?q?AudioManager=20Node:=20Dateien=20auflisten,=20a?= =?UTF-8?q?bspielen,=20l=C3=B6schen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neuer Node zeigt alle generierten Audio-Dateien mit Größe und Play-Button. Drei Modi: - Dateien auflisten (mit Abspielen im Browser) - Alle löschen - Nur Originale löschen (48kHz behalten) Co-Authored-By: Claude Opus 4.6 --- comfyui-audio/comfyui_audiocraft/nodes.py | 58 +++++++++++ .../comfyui_audiocraft/web/js/audioManager.js | 95 +++++++++++++++++++ comfyui-audio/workflow_sfx_optimized.json | 17 +++- 3 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 comfyui-audio/comfyui_audiocraft/web/js/audioManager.js diff --git a/comfyui-audio/comfyui_audiocraft/nodes.py b/comfyui-audio/comfyui_audiocraft/nodes.py index 81df238..11b7fbd 100644 --- a/comfyui-audio/comfyui_audiocraft/nodes.py +++ b/comfyui-audio/comfyui_audiocraft/nodes.py @@ -226,12 +226,69 @@ class AudioPreviewNode: return {"ui": {"audio": [{"filename": os.path.basename(audio_file), "subfolder": "audio", "type": "output"}]}} +class AudioManagerNode: + """Zeigt alle generierten Audio-Dateien an und kann sie löschen""" + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "action": (["Dateien auflisten", "Alle loeschen", "Nur Original loeschen (behalte 48kHz)"],), + } + } + + RETURN_TYPES = () + FUNCTION = "manage" + CATEGORY = "AudioCraft" + OUTPUT_NODE = True + + def manage(self, action): + audio_dir = "/app/ComfyUI/output/audio" + if not os.path.exists(audio_dir): + return {"ui": {"text": [{"content": "Kein Audio-Ordner vorhanden."}], "files": []}} + + 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), "path": fpath}) + + if action == "Alle loeschen": + deleted = 0 + for f in files: + os.remove(f["path"]) + deleted += 1 + print(f"[AudioManager] {deleted} Dateien geloescht ({total_size / 1024 / 1024:.1f} MB)") + return {"ui": {"text": [{"content": f"{deleted} Dateien geloescht ({total_size / 1024 / 1024:.1f} MB frei)"}], "files": []}} + + elif action == "Nur Original loeschen (behalte 48kHz)": + deleted = 0 + freed = 0 + keep = [] + for f in files: + if "_48000hz" not in f["name"]: + os.remove(f["path"]) + deleted += 1 + freed += f["size_mb"] + else: + keep.append(f) + print(f"[AudioManager] {deleted} Originale geloescht ({freed:.1f} MB frei)") + return {"ui": {"text": [{"content": f"{deleted} Originale geloescht ({freed:.1f} MB frei), {len(keep)} 48kHz Dateien behalten"}], "files": keep}} + + # Auflisten + print(f"[AudioManager] {len(files)} Dateien, {total_size / 1024 / 1024:.1f} MB") + return {"ui": {"text": [{"content": f"{len(files)} Dateien | {total_size / 1024 / 1024:.1f} MB"}], "files": files}} + + NODE_CLASS_MAPPINGS = { "MusicGen": MusicGenNode, "MusicGenLong": MusicGenLongNode, "AudioGen": AudioGenNode, "AudioUpsample": AudioUpsampleNode, "AudioPreview": AudioPreviewNode, + "AudioManager": AudioManagerNode, } NODE_DISPLAY_NAME_MAPPINGS = { @@ -240,4 +297,5 @@ NODE_DISPLAY_NAME_MAPPINGS = { "AudioGen": "AudioGen (Sound Effects)", "AudioUpsample": "Audio Upsample (Qualität)", "AudioPreview": "Audio Preview", + "AudioManager": "Audio Manager (Dateien)", } diff --git a/comfyui-audio/comfyui_audiocraft/web/js/audioManager.js b/comfyui-audio/comfyui_audiocraft/web/js/audioManager.js new file mode 100644 index 0000000..1a7a177 --- /dev/null +++ b/comfyui-audio/comfyui_audiocraft/web/js/audioManager.js @@ -0,0 +1,95 @@ +import { app } from "../../../scripts/app.js"; +import { api } from "../../../scripts/api.js"; + +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) { + // Update existing widget content + this._managerContainer.innerHTML = ""; + buildContent(this._managerContainer, summary, files); + const h = Math.min(60 + files.length * 22, 500); + this._managerWidget.computeSize = () => [this.size[0], h]; + this.setSize([this.size[0], h + 80]); + } else { + const container = document.createElement("div"); + container.style.cssText = "padding:4px;overflow-y:auto;max-height:450px;"; + buildContent(container, summary, files); + + this._managerContainer = container; + this._managerWidget = this.addDOMWidget("audio_manager", "dom", container, { + serialize: false, + hideOnZoom: false, + }); + const h = Math.min(60 + files.length * 22, 500); + this._managerWidget.computeSize = () => [this.size[0], h]; + this.setSize([this.size[0], h + 80]); + } + + app.graph.setDirtyCanvas(true); + }; + } + }, +}); + +function buildContent(container, summary, files) { + const header = document.createElement("div"); + header.textContent = summary; + header.style.cssText = "font-weight:bold;margin-bottom:6px;color:#fff;font-size:12px;"; + container.appendChild(header); + + if (files.length === 0) return; + + for (const f of files) { + const row = document.createElement("div"); + row.style.cssText = "display:flex;justify-content:space-between;padding:2px 0;font-size:11px;color:#ccc;border-bottom:1px solid #333;align-items:center;"; + + const name = document.createElement("span"); + name.textContent = f.name; + name.style.cssText = "overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;margin-right:8px;"; + + const size = document.createElement("span"); + size.textContent = f.size_mb + " MB"; + size.style.cssText = "color:#8cf;min-width:60px;text-align:right;margin-right:8px;"; + + const playBtn = document.createElement("button"); + playBtn.textContent = "\u25B6"; + playBtn.style.cssText = "background:#444;border:none;color:#8f8;cursor:pointer;padding:1px 6px;border-radius:3px;font-size:10px;"; + + const audioSrc = api.apiURL( + `/view?filename=${encodeURIComponent(f.name)}&subfolder=audio&type=output` + ); + + let currentAudio = null; + playBtn.onclick = () => { + if (currentAudio) { + currentAudio.pause(); + currentAudio = null; + playBtn.textContent = "\u25B6"; + return; + } + currentAudio = new Audio(audioSrc); + currentAudio.play(); + playBtn.textContent = "\u25A0"; + currentAudio.onended = () => { + playBtn.textContent = "\u25B6"; + currentAudio = null; + }; + }; + + row.appendChild(name); + row.appendChild(size); + row.appendChild(playBtn); + container.appendChild(row); + } +} diff --git a/comfyui-audio/workflow_sfx_optimized.json b/comfyui-audio/workflow_sfx_optimized.json index 96f8363..577dbf5 100644 --- a/comfyui-audio/workflow_sfx_optimized.json +++ b/comfyui-audio/workflow_sfx_optimized.json @@ -1,5 +1,5 @@ { - "last_node_id": 13, + "last_node_id": 14, "last_link_id": 9, "nodes": [ { @@ -178,7 +178,20 @@ "inputs": [], "outputs": [], "properties": {}, - "widgets_values": ["=== AudioGen SFX Guide (Meta Doku) ===\n\nModell: facebook/audiogen-medium (1.5B)\nTrainiert auf: 10s Segmente, 16kHz\nTrainingsdaten: Sonniss Game Effects,\n BBC SFX, AudioSet, AudioCaps u.a.\n\n--- Parameter pro SFX-Typ ---\n\nAttacken / Hits (1-2s):\n temperature 0.7 - 0.8\n cfg_coef 4.0 - 5.0\n top_k 150 - 200\n → scharf, praezise, wiederholbar\n\nFaehigkeiten / Spells (2-3s):\n temperature 0.9 - 1.0\n cfg_coef 3.0 - 4.0\n top_k 200 - 250\n → etwas mehr Variation erlaubt\n\nAmbient / Umgebung (5-10s):\n temperature 1.0 - 1.2\n cfg_coef 3.0\n top_k 250\n → natuerlich, organisch\n\nUI Sounds (0.5-1s):\n temperature 0.7\n cfg_coef 5.0\n top_k 150\n → kurz, knackig, konsistent\n\n--- Allgemeine Parameter ---\n\ntemperature:\n < 0.8 = fokussiert, deterministisch\n 1.0 = Standard, ausgewogen\n > 1.2 = mehr Variation, organischer\n\ncfg_coef:\n 3.0 = Standard (gut getestet)\n 4-5 = staerkere Prompt-Treue\n > 7 = Artefakte moeglich\n\ntop_k:\n 150 = eingeschraenkt, praezise\n 250 = Standard, gute Balance\n > 300 = zu viele Moeglichkeiten\n\nduration:\n Max Qualitaet bei <= 10s (Training)\n > 10s nutzt Sliding Window (Naehte)\n\n--- Prompt Aufbau ---\n\nSchema: [Aktion] [Material] [Detail] [Ort]\n\nBeispiele:\n heavy metal sword striking stone shield\n wooden door creaking open slowly dungeon\n fire crackling campfire burning wood\n metal chains rattling on stone floor\n footsteps echoing in stone corridor\n glass shattering breaking impact\n\nTipps:\n - Physikalisch beschreiben\n - Material + Groesse + Geschwindigkeit\n - Umgebung/Akustik angeben\n - Englisch (Trainingsdaten)\n - Kurz: 5-10 Woerter\n - 'sound effect' am Ende hilft\n - KEINE Musik-Begriffe\n - KEINE Vocals (nicht trainiert)\n\n--- Workflow Tipps ---\n\n - 3-5x generieren, bestes waehlen\n - Immer Upsample auf 48kHz\n - Verschiedene Seeds probieren\n - Mute (M) fuer inaktive Pipelines"] + "widgets_values": ["=== AudioGen SFX Guide ===\n\nModell: facebook/audiogen-medium (1.5B)\nTrainiert auf: 10s Segmente, 16kHz\nTrainingsdaten: Sonniss Game Effects,\n BBC SFX, AudioSet, AudioCaps u.a.\n\n--- Parameter pro SFX-Typ ---\n\nAttacken / Hits (1-2s):\n temperature 0.7 - 0.8\n cfg_coef 4.0 - 5.0\n top_k 150 - 200\n → scharf, praezise, wiederholbar\n\nFaehigkeiten / Spells (2-3s):\n temperature 0.9 - 1.0\n cfg_coef 3.0 - 4.0\n top_k 200 - 250\n → etwas mehr Variation erlaubt\n\nAmbient / Umgebung (5-10s):\n temperature 1.0 - 1.2\n cfg_coef 3.0\n top_k 250\n → natuerlich, organisch\n\nUI Sounds (0.5-1s):\n temperature 0.7\n cfg_coef 5.0\n top_k 150\n → kurz, knackig, konsistent\n\n--- Allgemeine Parameter ---\n\ntemperature:\n < 0.8 = fokussiert, deterministisch\n 1.0 = Standard, ausgewogen\n > 1.2 = mehr Variation, organischer\n\ncfg_coef:\n 3.0 = Standard (gut getestet)\n 4-5 = staerkere Prompt-Treue\n > 7 = Artefakte moeglich\n\ntop_k:\n 150 = eingeschraenkt, praezise\n 250 = Standard, gute Balance\n > 300 = zu viele Moeglichkeiten\n\nduration:\n Max Qualitaet bei <= 10s (Training)\n > 10s nutzt Sliding Window (Naehte)\n\n--- Prompt Aufbau ---\n\nSchema: [Aktion] [Material] [Detail] [Ort]\n\nBeispiele:\n heavy metal sword striking stone shield\n wooden door creaking open slowly dungeon\n fire crackling campfire burning wood\n metal chains rattling on stone floor\n footsteps echoing in stone corridor\n glass shattering breaking impact\n\nTipps:\n - Physikalisch beschreiben\n - Material + Groesse + Geschwindigkeit\n - Umgebung/Akustik angeben\n - Englisch (Trainingsdaten)\n - Kurz: 5-10 Woerter\n - 'sound effect' am Ende hilft\n - KEINE Musik-Begriffe\n - KEINE Vocals (nicht trainiert)\n\n--- Workflow Tipps ---\n\n - 3-5x generieren, bestes waehlen\n - Immer Upsample auf 48kHz\n - Verschiedene Seeds probieren\n - Mute (M) fuer inaktive Pipelines"] + }, + { + "id": 14, + "type": "AudioManager", + "pos": [1150, 940], + "size": {"0": 520, "1": 400}, + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [], + "outputs": [], + "properties": {"Node name for S&R": "AudioManager"}, + "widgets_values": ["Dateien auflisten"] } ], "links": [