AudioManager Node: Dateien auflisten, abspielen, löschen

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 <noreply@anthropic.com>
This commit is contained in:
Andre 2026-03-21 17:58:26 +01:00
parent 9e0e541d7e
commit b5af85a890
3 changed files with 168 additions and 2 deletions

View file

@ -226,12 +226,69 @@ class AudioPreviewNode:
return {"ui": {"audio": [{"filename": os.path.basename(audio_file), "subfolder": "audio", "type": "output"}]}} 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 = { NODE_CLASS_MAPPINGS = {
"MusicGen": MusicGenNode, "MusicGen": MusicGenNode,
"MusicGenLong": MusicGenLongNode, "MusicGenLong": MusicGenLongNode,
"AudioGen": AudioGenNode, "AudioGen": AudioGenNode,
"AudioUpsample": AudioUpsampleNode, "AudioUpsample": AudioUpsampleNode,
"AudioPreview": AudioPreviewNode, "AudioPreview": AudioPreviewNode,
"AudioManager": AudioManagerNode,
} }
NODE_DISPLAY_NAME_MAPPINGS = { NODE_DISPLAY_NAME_MAPPINGS = {
@ -240,4 +297,5 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"AudioGen": "AudioGen (Sound Effects)", "AudioGen": "AudioGen (Sound Effects)",
"AudioUpsample": "Audio Upsample (Qualität)", "AudioUpsample": "Audio Upsample (Qualität)",
"AudioPreview": "Audio Preview", "AudioPreview": "Audio Preview",
"AudioManager": "Audio Manager (Dateien)",
} }

View file

@ -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);
}
}

View file

@ -1,5 +1,5 @@
{ {
"last_node_id": 13, "last_node_id": 14,
"last_link_id": 9, "last_link_id": 9,
"nodes": [ "nodes": [
{ {
@ -178,7 +178,20 @@
"inputs": [], "inputs": [],
"outputs": [], "outputs": [],
"properties": {}, "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": [ "links": [