Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
chenpangpang
ComfyUI
Commits
a657f96c
Commit
a657f96c
authored
Nov 23, 2023
by
comfyanonymous
Browse files
Add a node to save animated webp.
parent
87031a19
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
79 additions
and
1 deletion
+79
-1
comfy_extras/nodes_images.py
comfy_extras/nodes_images.py
+76
-0
web/scripts/pnginfo.js
web/scripts/pnginfo.js
+3
-1
No files found.
comfy_extras/nodes_images.py
View file @
a657f96c
import
nodes
import
nodes
import
folder_paths
from
comfy.cli_args
import
args
from
PIL
import
Image
import
numpy
as
np
import
json
import
os
MAX_RESOLUTION
=
nodes
.
MAX_RESOLUTION
MAX_RESOLUTION
=
nodes
.
MAX_RESOLUTION
class
ImageCrop
:
class
ImageCrop
:
...
@@ -38,7 +46,75 @@ class RepeatImageBatch:
...
@@ -38,7 +46,75 @@ class RepeatImageBatch:
s
=
image
.
repeat
((
amount
,
1
,
1
,
1
))
s
=
image
.
repeat
((
amount
,
1
,
1
,
1
))
return
(
s
,)
return
(
s
,)
class
SaveAnimatedWEBP
:
def
__init__
(
self
):
self
.
output_dir
=
folder_paths
.
get_output_directory
()
self
.
type
=
"output"
self
.
prefix_append
=
""
methods
=
{
"default"
:
4
,
"fastest"
:
0
,
"slowest"
:
6
}
@
classmethod
def
INPUT_TYPES
(
s
):
return
{
"required"
:
{
"images"
:
(
"IMAGE"
,
),
"filename_prefix"
:
(
"STRING"
,
{
"default"
:
"ComfyUI"
}),
"fps"
:
(
"FLOAT"
,
{
"default"
:
6.0
,
"min"
:
0.01
,
"max"
:
1000.0
,
"step"
:
0.01
}),
"lossless"
:
(
"BOOLEAN"
,
{
"default"
:
True
}),
"quality"
:
(
"INT"
,
{
"default"
:
80
,
"min"
:
0
,
"max"
:
100
}),
"method"
:
(
list
(
s
.
methods
.
keys
()),),
# "num_frames": ("INT", {"default": 0, "min": 0, "max": 8192}),
},
"hidden"
:
{
"prompt"
:
"PROMPT"
,
"extra_pnginfo"
:
"EXTRA_PNGINFO"
},
}
RETURN_TYPES
=
()
FUNCTION
=
"save_images"
OUTPUT_NODE
=
True
CATEGORY
=
"_for_testing"
def
save_images
(
self
,
images
,
fps
,
filename_prefix
,
lossless
,
quality
,
method
,
num_frames
=
0
,
prompt
=
None
,
extra_pnginfo
=
None
):
method
=
self
.
methods
.
get
(
method
,
"aoeu"
)
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
()
pil_images
=
[]
for
image
in
images
:
i
=
255.
*
image
.
cpu
().
numpy
()
img
=
Image
.
fromarray
(
np
.
clip
(
i
,
0
,
255
).
astype
(
np
.
uint8
))
pil_images
.
append
(
img
)
metadata
=
None
if
not
args
.
disable_metadata
:
metadata
=
pil_images
[
0
].
getexif
()
if
prompt
is
not
None
:
metadata
[
0x0110
]
=
"prompt:{}"
.
format
(
json
.
dumps
(
prompt
))
if
extra_pnginfo
is
not
None
:
inital_exif
=
0x010f
for
x
in
extra_pnginfo
:
metadata
[
inital_exif
]
=
"{}:{}"
.
format
(
x
,
json
.
dumps
(
extra_pnginfo
[
x
]))
inital_exif
-=
1
if
num_frames
==
0
:
num_frames
=
len
(
pil_images
)
c
=
len
(
pil_images
)
for
i
in
range
(
0
,
c
,
num_frames
):
file
=
f
"
{
filename
}
_
{
counter
:
05
}
_.webp"
pil_images
[
i
].
save
(
os
.
path
.
join
(
full_output_folder
,
file
),
save_all
=
True
,
duration
=
int
(
1000.0
/
fps
),
append_images
=
pil_images
[
i
+
1
:
i
+
num_frames
],
exif
=
metadata
,
lossless
=
lossless
,
quality
=
quality
,
method
=
method
)
results
.
append
({
"filename"
:
file
,
"subfolder"
:
subfolder
,
"type"
:
self
.
type
})
counter
+=
1
animated
=
num_frames
!=
1
return
{
"ui"
:
{
"images"
:
results
,
"animated"
:
(
animated
,)
}
}
NODE_CLASS_MAPPINGS
=
{
NODE_CLASS_MAPPINGS
=
{
"ImageCrop"
:
ImageCrop
,
"ImageCrop"
:
ImageCrop
,
"RepeatImageBatch"
:
RepeatImageBatch
,
"RepeatImageBatch"
:
RepeatImageBatch
,
"SaveAnimatedWEBP"
:
SaveAnimatedWEBP
,
}
}
web/scripts/pnginfo.js
View file @
a657f96c
...
@@ -50,7 +50,6 @@ export function getPngMetadata(file) {
...
@@ -50,7 +50,6 @@ export function getPngMetadata(file) {
function
parseExifData
(
exifData
)
{
function
parseExifData
(
exifData
)
{
// Check for the correct TIFF header (0x4949 for little-endian or 0x4D4D for big-endian)
// Check for the correct TIFF header (0x4949 for little-endian or 0x4D4D for big-endian)
const
isLittleEndian
=
new
Uint16Array
(
exifData
.
slice
(
0
,
2
))[
0
]
===
0x4949
;
const
isLittleEndian
=
new
Uint16Array
(
exifData
.
slice
(
0
,
2
))[
0
]
===
0x4949
;
console
.
log
(
exifData
);
// Function to read 16-bit and 32-bit integers from binary data
// Function to read 16-bit and 32-bit integers from binary data
function
readInt
(
offset
,
isLittleEndian
,
length
)
{
function
readInt
(
offset
,
isLittleEndian
,
length
)
{
...
@@ -126,6 +125,9 @@ export function getWebpMetadata(file) {
...
@@ -126,6 +125,9 @@ export function getWebpMetadata(file) {
const
chunk_length
=
dataView
.
getUint32
(
offset
+
4
,
true
);
const
chunk_length
=
dataView
.
getUint32
(
offset
+
4
,
true
);
const
chunk_type
=
String
.
fromCharCode
(...
webp
.
slice
(
offset
,
offset
+
4
));
const
chunk_type
=
String
.
fromCharCode
(...
webp
.
slice
(
offset
,
offset
+
4
));
if
(
chunk_type
===
"
EXIF
"
)
{
if
(
chunk_type
===
"
EXIF
"
)
{
if
(
String
.
fromCharCode
(...
webp
.
slice
(
offset
+
8
,
offset
+
8
+
6
))
==
"
Exif
\
0
\
0
"
)
{
offset
+=
6
;
}
let
data
=
parseExifData
(
webp
.
slice
(
offset
+
8
,
offset
+
8
+
chunk_length
));
let
data
=
parseExifData
(
webp
.
slice
(
offset
+
8
,
offset
+
8
+
chunk_length
));
for
(
var
key
in
data
)
{
for
(
var
key
in
data
)
{
var
value
=
data
[
key
];
var
value
=
data
[
key
];
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment