Skip to content

Commit

Permalink
feat: ✨ use PIL for gif saving
Browse files Browse the repository at this point in the history
  • Loading branch information
melMass committed Aug 5, 2023
1 parent 0fb2d4d commit 2bc7ae8
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 60 deletions.
115 changes: 68 additions & 47 deletions nodes/io.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from ..utils import tensor2np
from ..utils import tensor2np, PIL_FILTER_MAP
import uuid
import folder_paths
from ..log import log
Expand All @@ -7,6 +7,8 @@
import torch
from pathlib import Path
import numpy as np
from PIL import Image
from typing import Optional, List


class ExportToProres:
Expand Down Expand Up @@ -37,9 +39,9 @@ def export_prores(
if images.size(0) == 0:
return ("",)
output_dir = Path(folder_paths.get_output_directory())
id = f"{prefix}_{uuid.uuid4()}.mov"
file_id = f"{prefix}_{uuid.uuid4()}.mov"

log.debug(f"Exporting to {output_dir / id}")
log.debug(f"Exporting to {output_dir / file_id}")

frames = tensor2np(images)
log.debug(f"Frames type {type(frames[0])}")
Expand All @@ -49,7 +51,7 @@ def export_prores(

height, width, _ = frames[0].shape

out_path = (output_dir / id).as_posix()
out_path = (output_dir / file_id).as_posix()

# Prepare the FFmpeg command
command = [
Expand Down Expand Up @@ -91,6 +93,37 @@ def export_prores(
return (out_path,)


def prepare_animated_batch(
batch: torch.Tensor,
pingpong=False,
resize_by=1.0,
resample_filter: Optional[Image.Resampling] = None,
image_type=np.uint8,
) -> List[Image.Image]:
images = tensor2np(batch)
images = [frame.astype(image_type) for frame in images]

height, width, _ = batch[0].shape

if pingpong:
reversed_frames = images[::-1]
images.extend(reversed_frames)
pil_images = [Image.fromarray(frame) for frame in images]

# Resize frames if necessary
if abs(resize_by - 1.0) > 1e-6:
new_width = int(width * resize_by)
new_height = int(height * resize_by)
pil_images_resized = [
frame.resize((new_width, new_height), resample=resample_filter)
for frame in pil_images
]
pil_images = pil_images_resized

return pil_images


# todo: deprecate for apng
class SaveGif:
"""Save the images from the batch as a GIF"""

Expand All @@ -103,66 +136,54 @@ def INPUT_TYPES(cls):
"resize_by": ("FLOAT", {"default": 1.0, "min": 0.1}),
"optimize": ("BOOLEAN", {"default": False}),
"pingpong": ("BOOLEAN", {"default": False}),
}
},
"optional": {
"resample_filter": (list(PIL_FILTER_MAP.keys()),),
},
}

RETURN_TYPES = ()
OUTPUT_NODE = True
CATEGORY = "mtb/IO"
FUNCTION = "save_gif"

def save_gif(self, image, fps=12, resize_by=1.0, pingpong=False):
def save_gif(
self,
image,
fps=12,
resize_by=1.0,
optimize=False,
pingpong=False,
resample_filter=None,
):
if image.size(0) == 0:
return ("",)

images = tensor2np(image)
images = [frame.astype(np.uint8) for frame in images]
if pingpong:
reversed_frames = images[::-1]
images.extend(reversed_frames)
if resample_filter is not None:
resample_filter = PIL_FILTER_MAP.get(resample_filter)

height, width, _ = image[0].shape
pil_images = prepare_animated_batch(
image,
pingpong,
resize_by,
resample_filter,
)

ruuid = uuid.uuid4()

ruuid = ruuid.hex[:10]

out_path = f"{folder_paths.output_directory}/{ruuid}.gif"

log.debug(f"Saving a gif file {width}x{height} as {ruuid}.gif")

# Prepare the FFmpeg command
command = [
"ffmpeg",
"-y",
"-f",
"rawvideo",
"-vcodec",
"rawvideo",
"-s",
f"{width}x{height}",
"-pix_fmt",
"rgb24", # GIF only supports rgb24
"-r",
str(fps),
"-i",
"-",
"-vf",
f"fps={fps},scale={width * resize_by}:-1", # Set frame rate and resize if necessary
"-y",
# Create the GIF from PIL images
pil_images[0].save(
out_path,
]

process = subprocess.Popen(command, stdin=subprocess.PIPE)

for frame in images:
model_management.throw_exception_if_processing_interrupted()
process.stdin.write(frame.tobytes())

process.stdin.close()
process.wait()
results = []
results.append({"filename": f"{ruuid}.gif", "subfolder": "", "type": "output"})
save_all=True,
append_images=pil_images[1:],
optimize=optimize,
duration=int(1000 / fps),
loop=0,
)

results = [{"filename": f"{ruuid}.gif", "subfolder": "", "type": "output"}]
return {"ui": {"gif": results}}


Expand Down
31 changes: 18 additions & 13 deletions utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,35 +50,42 @@ def import_install(package_name):
# endregion

# region GLOBAL VARIABLES
# Get the absolute path of the parent directory of the current script
# - Get the absolute path of the parent directory of the current script
here = Path(__file__).parent.resolve()

# Construct the absolute path to the ComfyUI directory
# - Construct the absolute path to the ComfyUI directory
comfy_dir = here.parent.parent

# Construct the path to the font file
# - Construct the path to the font file
font_path = here / "font.ttf"

# Add extern folder to path
# - Add extern folder to path
extern_root = here / "extern"
add_path(extern_root)
for pth in extern_root.iterdir():
if pth.is_dir():
add_path(pth)


# Add the ComfyUI directory and custom nodes path to the sys.path list
# - Add the ComfyUI directory and custom nodes path to the sys.path list
add_path(comfy_dir)
add_path((comfy_dir / "custom_nodes"))

PIL_FILTER_MAP = {
"nearest": Image.Resampling.NEAREST,
"box": Image.Resampling.BOX,
"bilinear": Image.Resampling.BILINEAR,
"hamming": Image.Resampling.HAMMING,
"bicubic": Image.Resampling.BICUBIC,
"lanczos": Image.Resampling.LANCZOS,
}


# endregion


# region TENSOR UTILITIES
def tensor2pil(image: torch.Tensor) -> List[Image.Image]:
batch_count = 1
if len(image.shape) > 3:
batch_count = image.size(0)

batch_count = image.size(0) if len(image.shape) > 3 else 1
if batch_count > 1:
out = []
for i in range(batch_count):
Expand Down Expand Up @@ -107,9 +114,7 @@ def np2tensor(img_np: np.ndarray | List[np.ndarray]) -> torch.Tensor:


def tensor2np(tensor: torch.Tensor) -> List[np.ndarray]:
batch_count = 1
if len(tensor.shape) > 3:
batch_count = tensor.size(0)
batch_count = tensor.size(0) if len(tensor.shape) > 3 else 1
if batch_count > 1:
out = []
for i in range(batch_count):
Expand Down

0 comments on commit 2bc7ae8

Please sign in to comment.