Skip to content

Commit 4650e7d

Browse files
Save and load workflow from the flac files output by SaveAudio.
1 parent 3b423af commit 4650e7d

File tree

4 files changed

+164
-4
lines changed

4 files changed

+164
-4
lines changed

comfy_extras/nodes_audio.py

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
import comfy.model_management
44
import folder_paths
55
import os
6+
import io
7+
import json
8+
import struct
9+
from comfy.cli_args import args
610

711
class EmptyLatentAudio:
812
def __init__(self):
@@ -53,6 +57,61 @@ def decode(self, vae, samples):
5357
audio = vae.decode(samples["samples"]).movedim(-1, 1)
5458
return ({"waveform": audio, "sample_rate": 44100}, )
5559

60+
61+
def create_vorbis_comment_block(comment_dict, last_block):
62+
vendor_string = b'ComfyUI'
63+
vendor_length = len(vendor_string)
64+
65+
comments = []
66+
for key, value in comment_dict.items():
67+
comment = f"{key}={value}".encode('utf-8')
68+
comments.append(struct.pack('<I', len(comment)) + comment)
69+
70+
user_comment_list_length = len(comments)
71+
user_comments = b''.join(comments)
72+
73+
comment_data = struct.pack('<I', vendor_length) + vendor_string + struct.pack('<I', user_comment_list_length) + user_comments
74+
if last_block:
75+
id = b'\x84'
76+
else:
77+
id = b'\x04'
78+
comment_block = id + struct.pack('>I', len(comment_data))[1:] + comment_data
79+
80+
return comment_block
81+
82+
def insert_or_replace_vorbis_comment(flac_io, comment_dict):
83+
if len(comment_dict) == 0:
84+
return flac_io
85+
86+
flac_io.seek(4)
87+
88+
blocks = []
89+
last_block = False
90+
91+
while not last_block:
92+
header = flac_io.read(4)
93+
last_block = (header[0] & 0x80) != 0
94+
block_type = header[0] & 0x7F
95+
block_length = struct.unpack('>I', b'\x00' + header[1:])[0]
96+
block_data = flac_io.read(block_length)
97+
98+
if block_type == 4 or block_type == 1:
99+
pass
100+
else:
101+
header = bytes([(header[0] & (~0x80))]) + header[1:]
102+
blocks.append(header + block_data)
103+
104+
blocks.append(create_vorbis_comment_block(comment_dict, last_block=True))
105+
106+
new_flac_io = io.BytesIO()
107+
new_flac_io.write(b'fLaC')
108+
for block in blocks:
109+
new_flac_io.write(block)
110+
111+
new_flac_io.write(flac_io.read())
112+
return new_flac_io
113+
114+
56115
class SaveAudio:
57116
def __init__(self):
58117
self.output_dir = folder_paths.get_output_directory()
@@ -78,11 +137,27 @@ def save_audio(self, audio, filename_prefix="ComfyUI", prompt=None, extra_pnginf
78137
filename_prefix += self.prefix_append
79138
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir)
80139
results = list()
140+
141+
metadata = {}
142+
if not args.disable_metadata:
143+
if prompt is not None:
144+
metadata["prompt"] = json.dumps(prompt)
145+
if extra_pnginfo is not None:
146+
for x in extra_pnginfo:
147+
metadata[x] = json.dumps(extra_pnginfo[x])
148+
81149
for (batch_number, waveform) in enumerate(audio["waveform"]):
82-
#TODO: metadata
83150
filename_with_batch_num = filename.replace("%batch_num%", str(batch_number))
84151
file = f"{filename_with_batch_num}_{counter:05}_.flac"
85-
torchaudio.save(os.path.join(full_output_folder, file), waveform, audio["sample_rate"], format="FLAC")
152+
153+
buff = io.BytesIO()
154+
torchaudio.save(buff, waveform, audio["sample_rate"], format="FLAC")
155+
156+
buff = insert_or_replace_vorbis_comment(buff, metadata)
157+
158+
with open(os.path.join(full_output_folder, file), 'wb') as f:
159+
f.write(buff.getbuffer())
160+
86161
results.append({
87162
"filename": file,
88163
"subfolder": subfolder,

web/scripts/app.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ComfyWidgets, initWidgets } from "./widgets.js";
33
import { ComfyUI, $el } from "./ui.js";
44
import { api } from "./api.js";
55
import { defaultGraph } from "./defaultGraph.js";
6-
import { getPngMetadata, getWebpMetadata, importA1111, getLatentMetadata } from "./pnginfo.js";
6+
import { getPngMetadata, getWebpMetadata, getFlacMetadata, importA1111, getLatentMetadata } from "./pnginfo.js";
77
import { addDomClippingSetting } from "./domWidget.js";
88
import { createImageHost, calculateImageGrid } from "./ui/imagePreview.js";
99
import { ComfyAppMenu } from "./ui/menu/index.js";
@@ -2277,6 +2277,19 @@ export class ComfyApp {
22772277
const workflow = pngInfo?.workflow || pngInfo?.Workflow;
22782278
const prompt = pngInfo?.prompt || pngInfo?.Prompt;
22792279

2280+
if (workflow) {
2281+
this.loadGraphData(JSON.parse(workflow), true, true, fileName);
2282+
} else if (prompt) {
2283+
this.loadApiJson(JSON.parse(prompt), fileName);
2284+
} else {
2285+
this.showErrorOnFileLoad(file);
2286+
}
2287+
} else if (file.type === "audio/flac") {
2288+
const pngInfo = await getFlacMetadata(file);
2289+
// Support loading workflows from that webp custom node.
2290+
const workflow = pngInfo?.workflow;
2291+
const prompt = pngInfo?.prompt;
2292+
22802293
if (workflow) {
22812294
this.loadGraphData(JSON.parse(workflow), true, true, fileName);
22822295
} else if (prompt) {

web/scripts/pnginfo.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,78 @@ export function getLatentMetadata(file) {
163163
});
164164
}
165165

166+
167+
function getString(dataView, offset, length) {
168+
let string = '';
169+
for (let i = 0; i < length; i++) {
170+
string += String.fromCharCode(dataView.getUint8(offset + i));
171+
}
172+
return string;
173+
}
174+
175+
// Function to parse the Vorbis Comment block
176+
function parseVorbisComment(dataView) {
177+
let offset = 0;
178+
const vendorLength = dataView.getUint32(offset, true);
179+
offset += 4;
180+
const vendorString = getString(dataView, offset, vendorLength);
181+
offset += vendorLength;
182+
183+
const userCommentListLength = dataView.getUint32(offset, true);
184+
offset += 4;
185+
const comments = {};
186+
for (let i = 0; i < userCommentListLength; i++) {
187+
const commentLength = dataView.getUint32(offset, true);
188+
offset += 4;
189+
const comment = getString(dataView, offset, commentLength);
190+
offset += commentLength;
191+
192+
const [key, value] = comment.split('=');
193+
194+
comments[key] = value;
195+
}
196+
197+
return comments;
198+
}
199+
200+
// Function to read a FLAC file and parse Vorbis comments
201+
export function getFlacMetadata(file) {
202+
return new Promise((r) => {
203+
const reader = new FileReader();
204+
reader.onload = function(event) {
205+
const arrayBuffer = event.target.result;
206+
const dataView = new DataView(arrayBuffer);
207+
208+
// Verify the FLAC signature
209+
const signature = String.fromCharCode(...new Uint8Array(arrayBuffer, 0, 4));
210+
if (signature !== 'fLaC') {
211+
console.error('Not a valid FLAC file');
212+
return;
213+
}
214+
215+
// Parse metadata blocks
216+
let offset = 4;
217+
let vorbisComment = null;
218+
while (offset < dataView.byteLength) {
219+
const isLastBlock = dataView.getUint8(offset) & 0x80;
220+
const blockType = dataView.getUint8(offset) & 0x7F;
221+
const blockSize = dataView.getUint32(offset, false) & 0xFFFFFF;
222+
offset += 4;
223+
224+
if (blockType === 4) { // Vorbis Comment block type
225+
vorbisComment = parseVorbisComment(new DataView(arrayBuffer, offset, blockSize));
226+
}
227+
228+
offset += blockSize;
229+
if (isLastBlock) break;
230+
}
231+
232+
r(vorbisComment);
233+
};
234+
reader.readAsArrayBuffer(file);
235+
});
236+
}
237+
166238
export async function importA1111(graph, parameters) {
167239
const p = parameters.lastIndexOf("\nSteps:");
168240
if (p > -1) {

web/scripts/ui.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ export class ComfyUI {
373373
const fileInput = $el("input", {
374374
id: "comfy-file-input",
375375
type: "file",
376-
accept: ".json,image/png,.latent,.safetensors,image/webp",
376+
accept: ".json,image/png,.latent,.safetensors,image/webp,audio/flac",
377377
style: {display: "none"},
378378
parent: document.body,
379379
onchange: () => {

0 commit comments

Comments
 (0)