This commit is contained in:
pkpk
2023-12-27 10:32:31 +09:00
parent a7ff7fa18c
commit 0b2dfe5b48
6 changed files with 223 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
# ComfyUI-SaveAVIF
A custom node on ComfyUI that saves images in AVIF format.
Workflow can be loaded from images saved at this node.
## Description:
It can be used in the same way as the Save Image node.
Note that encoding to AVIF takes a little time.
c_quality: Set quality for color (0 - 100, where 100 is lossless)
enc_speed: Encoder speed (0 - 10, slowest/smallest - fastest/largest)
When encoding is finished, the node outputs an image, which is a pass-through of the input.
This is intended for use such as notifying the end of encoding by connecting a node that plays a sound.
## Installation:
Install it using ComfyUI Manager.
Or use `git clone https://github.com/pkpkTech/ComfyUI-SaveAVIF` in your ComfyUI custom nodes directory
and `pip install -r requirements.txt`
+74
View File
@@ -0,0 +1,74 @@
import numpy as np
from PIL import Image
import pillow_avif
import folder_paths
import os
import json
from comfy.cli_args import args
class SaveAvif:
def __init__(self):
self.output_dir = folder_paths.get_output_directory()
self.type = "output"
self.prefix_append = ""
@classmethod
def INPUT_TYPES(s):
return {"required":
{"images": ("IMAGE", ),
"filename_prefix": ("STRING", {"default": "ComfyUI"}),
"c_quality":("INT", {"default": 75, "min": 0, "max": 100, "step": 1}),
"enc_speed":("INT", {"default": 6, "min": 0, "max": 10, "step": 1})},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image( pass-through )",)
FUNCTION = "save_avif"
OUTPUT_NODE = True
CATEGORY = "image"
def save_avif(self, images, filename_prefix="ComfyUI", c_quality=75, enc_speed=6, prompt=None, extra_pnginfo=None):
filename_prefix += self.prefix_append
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0])
results = list()
for image in images:
i = 255. * image.cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
img_exif = img.getexif()
if not args.disable_metadata:
if prompt is not None:
prompt_text = "".join(json.dumps(prompt))
img_exif[0x010f] ="Prompt:" + prompt_text
if extra_pnginfo is not None:
workflow_metadata = str()
for x in extra_pnginfo:
workflow_metadata += "".join(json.dumps(extra_pnginfo[x]))
img_exif[0x010e] = "Workflow:"+ workflow_metadata
file = f"{filename}_{counter:05}_.avif"
img.save(os.path.join(full_output_folder, file), quality= c_quality, speed= enc_speed, exif= img_exif)
results.append({
"filename": file,
"subfolder": subfolder,
"type": self.type
});
counter += 1
return { "result": (images,), "ui": { "images": results } }
NODE_CLASS_MAPPINGS = {
"SaveAvif": SaveAvif
}
NODE_DISPLAY_NAME_MAPPINGS = {
"SaveAvif": "Save AVIF"
}
+30
View File
@@ -0,0 +1,30 @@
import shutil
import folder_paths
import os, sys, subprocess
import filecmp
print("### Loading: Save AVIF")
comfy_path = os.path.dirname(folder_paths.__file__)
def setup_js():
self_path = os.path.dirname(__file__)
js_dest_path = os.path.join(comfy_path, "web", "extensions", "saveavif")
info_src_path = os.path.join(self_path, "web", "avifInfo.js")
sex_src_path = os.path.join(self_path, "web", "saveAvifExtraOutput.js")
## Creating folder if it's not present, then Copy.
print("Copying JS files for Workflow loading")
if (os.path.isdir(js_dest_path)==False):
os.mkdir(js_dest_path)
shutil.copy(info_src_path, js_dest_path)
shutil.copy(sex_src_path, js_dest_path)
setup_js()
from .SaveAvif import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
+1
View File
@@ -0,0 +1 @@
pillow-avif-plugin
+61
View File
@@ -0,0 +1,61 @@
import { app } from "/scripts/app.js";
app.registerExtension({
name: "pkpk.saveavif.prompt",
setup(app,file){
async function getAvifExifData(avifFile) {
const reader = new FileReader();
reader.readAsArrayBuffer(avifFile);
return new Promise((resolve, reject) => {
reader.onloadend = function() {
const buffer = reader.result;
const view = new DataView(buffer);
let offset = 0;
// Search for the "EXIF" or "Exif" tag
while (offset < view.byteLength - 4) {
const tag = view.getUint32(offset, true);
if (tag === 0x46495845 || tag === 0x66697845) {
const exifOffset = offset + 6;
const exifData = buffer.slice(exifOffset);
const exifString = new TextDecoder().decode(exifData).replaceAll(String.fromCharCode(0), '');
let exifJsonString = exifString.slice(exifString.indexOf("Workflow"));
let promptregex="(?<!\{)}Prompt:{(?![\w\s]*[\}])";
let exifJsonStringMap = new Map([
["workflow",exifJsonString.slice(9,exifJsonString.search(promptregex)+1)],
["prompt",exifJsonString.substring((exifJsonString.search(promptregex)+8))]
]);
let fullJson=Object.fromEntries(exifJsonStringMap);
resolve(fullJson);
}
offset++;
}
reject(new Error('EXIF metadata not found'));
}})};
const handleFile = app.handleFile;
app.handleFile = async function(file) {
if (file.type === "image/avif") {
const avifInfo =await getAvifExifData(file);
if (avifInfo) {
if (avifInfo.workflow) {
if(app.load_workflow_with_components) {
app.load_workflow_with_components(avifInfo.workflow);
}
else
this.loadGraphData(JSON.parse(avifInfo.workflow));
}
}
} else {
return handleFile.apply(this, arguments);
}
}
},});
+31
View File
@@ -0,0 +1,31 @@
import { app } from "../../scripts/app.js";
import { applyTextReplacements } from "../../scripts/utils.js";
app.registerExtension({
name: "pkpk.saveavif.exoutput",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "SaveAvif") {
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
const widget = this.widgets.find((w) => w.name === "filename_prefix");
widget.serializeValue = () => {
return applyTextReplacements(app, widget.value);
};
return r;
};
} else {
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
if (!this.properties || !("Node name for S&R" in this.properties)) {
this.addProperty("Node name for S&R", this.constructor.type, "string");
}
return r;
};
}
},
});