mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2025-07-07 15:57:10 +08:00
save mp4 and other optimization
This commit is contained in:
parent
7c7d9f54da
commit
4e788eb2ea
@ -1,6 +1,9 @@
|
|||||||
from .nodes_preprocessing import MD_LoadImageFromUrl, MD_CompressAdjustNode, MD_ImageToMotionPrompt
|
from .nodes_preprocessing import MD_LoadImageFromUrl, MD_CompressAdjustNode, MD_ImageToMotionPrompt
|
||||||
from .nodes_model import MD_LoadVideoModel, MD_ImgToVideo, MD_VideoSampler
|
from .nodes_model import MD_LoadVideoModel, MD_ImgToVideo, MD_VideoSampler
|
||||||
from .nodes_output import MD_SaveAnimatedWEBP, MD_SaveMP4
|
from .nodes_output import MD_SaveMP4
|
||||||
|
|
||||||
|
from .nodes_input import MD_VideoInputs
|
||||||
|
|
||||||
NODE_CLASS_MAPPINGS = {
|
NODE_CLASS_MAPPINGS = {
|
||||||
# PREPROCESSING
|
# PREPROCESSING
|
||||||
"Memedeck_ImageToMotionPrompt": MD_ImageToMotionPrompt,
|
"Memedeck_ImageToMotionPrompt": MD_ImageToMotionPrompt,
|
||||||
@ -12,7 +15,9 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
"Memedeck_VideoSampler": MD_VideoSampler,
|
"Memedeck_VideoSampler": MD_VideoSampler,
|
||||||
# POSTPROCESSING
|
# POSTPROCESSING
|
||||||
"Memedeck_SaveMP4": MD_SaveMP4,
|
"Memedeck_SaveMP4": MD_SaveMP4,
|
||||||
"Memedeck_SaveAnimatedWEBP": MD_SaveAnimatedWEBP
|
# "Memedeck_SaveAnimatedWEBP": MD_SaveAnimatedWEBP,
|
||||||
|
# INPUT NODES
|
||||||
|
"Memedeck_VideoInputs": MD_VideoInputs
|
||||||
# "Memedeck_SaveAnimatedGIF": MD_SaveAnimatedGIF
|
# "Memedeck_SaveAnimatedGIF": MD_SaveAnimatedGIF
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,6 +32,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
|||||||
"Memedeck_ImgToVideo": "MemeDeck: Image To Video",
|
"Memedeck_ImgToVideo": "MemeDeck: Image To Video",
|
||||||
"Memedeck_VideoSampler": "MemeDeck: Video Sampler",
|
"Memedeck_VideoSampler": "MemeDeck: Video Sampler",
|
||||||
# POSTPROCESSING
|
# POSTPROCESSING
|
||||||
"Memedeck_SaveMP4": "MemeDeck: Save MP4"
|
"Memedeck_SaveMP4": "MemeDeck: Save MP4",
|
||||||
|
# INPUT NODES
|
||||||
|
"Memedeck_VideoInputs": "MemeDeck: Video Inputs"
|
||||||
# "Memedeck_SaveAnimatedGIF": "MemeDeck: Save Animated GIF"
|
# "Memedeck_SaveAnimatedGIF": "MemeDeck: Save Animated GIF"
|
||||||
}
|
}
|
||||||
|
67
custom_nodes/MemedeckComfyNodes/nodes_input.py
Normal file
67
custom_nodes/MemedeckComfyNodes/nodes_input.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
from comfy_extras.nodes_custom_sampler import Noise_RandomNoise
|
||||||
|
|
||||||
|
|
||||||
|
class MD_VideoInputs:
|
||||||
|
"""One node to load all input parameters for video generation"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"image_url": (
|
||||||
|
"STRING",
|
||||||
|
),
|
||||||
|
"length": ("INT", {
|
||||||
|
"default": 121,
|
||||||
|
"description": "The length of the video."
|
||||||
|
}),
|
||||||
|
"steps": ("INT", {
|
||||||
|
"default": 25,
|
||||||
|
"description": "Number of steps to generate the video."
|
||||||
|
}),
|
||||||
|
"width": ("INT", {
|
||||||
|
"default": 768,
|
||||||
|
"description": "The width of the video."
|
||||||
|
}),
|
||||||
|
"height": ("INT", {
|
||||||
|
"default": 768,
|
||||||
|
"description": "The height of the video."
|
||||||
|
}),
|
||||||
|
"crf": ("INT", {
|
||||||
|
"default": 28,
|
||||||
|
"min": 0,
|
||||||
|
"max": 51,
|
||||||
|
"step": 1
|
||||||
|
}),
|
||||||
|
"terminal": ("FLOAT", {
|
||||||
|
"default": 0.1,
|
||||||
|
"step": 0.01,
|
||||||
|
"description": "The terminal values of the sigmas after stretching."
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"seed": (
|
||||||
|
"INT",
|
||||||
|
),
|
||||||
|
"user_prompt": (
|
||||||
|
"STRING",
|
||||||
|
),
|
||||||
|
"pre_prompt": (
|
||||||
|
"STRING",
|
||||||
|
),
|
||||||
|
"post_prompt": (
|
||||||
|
"STRING",
|
||||||
|
),
|
||||||
|
"negative_prompt": (
|
||||||
|
"STRING",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("STRING", "INT", "INT", "INT", "INT", "INT", "FLOAT", "STRING", "STRING", "STRING", "STRING", "NOISE",)
|
||||||
|
RETURN_NAMES = ("image_url", "length", "steps", "width", "height", "crf", "terminal", "user_prompt", "pre_prompt", "post_prompt", "negative_prompt", "seed")
|
||||||
|
FUNCTION = "load_inputs"
|
||||||
|
CATEGORY = "MemeDeck"
|
||||||
|
|
||||||
|
def load_inputs(self, image_url, length=121, steps=25, width=768, height=768, crf=28, terminal=0.1, user_prompt="", pre_prompt="", post_prompt="", negative_prompt="", seed=None):
|
||||||
|
return (image_url, length, steps, width, height, crf, terminal, user_prompt, pre_prompt, post_prompt, negative_prompt, Noise_RandomNoise(seed))
|
@ -1,5 +1,8 @@
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
import subprocess
|
||||||
import time
|
import time
|
||||||
|
import uuid
|
||||||
|
from custom_nodes.MemedeckComfyNodes.nodes_preprocessing import ffmpeg_process
|
||||||
import folder_paths
|
import folder_paths
|
||||||
from comfy.cli_args import args
|
from comfy.cli_args import args
|
||||||
import torch
|
import torch
|
||||||
@ -11,6 +14,7 @@ import numpy as np
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
from .lib import utils
|
||||||
|
|
||||||
# setup logger
|
# setup logger
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -23,7 +27,7 @@ WATERMARK = """
|
|||||||
"""
|
"""
|
||||||
WATERMARK_SIZE = 28
|
WATERMARK_SIZE = 28
|
||||||
|
|
||||||
class MD_SaveAnimatedWEBP:
|
class MD_SaveMP4:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.output_dir = folder_paths.get_output_directory()
|
self.output_dir = folder_paths.get_output_directory()
|
||||||
self.type = "output"
|
self.type = "output"
|
||||||
@ -37,9 +41,9 @@ class MD_SaveAnimatedWEBP:
|
|||||||
"images": ("IMAGE", ),
|
"images": ("IMAGE", ),
|
||||||
"filename_prefix": ("STRING", {"default": "memedeck_video"}),
|
"filename_prefix": ("STRING", {"default": "memedeck_video"}),
|
||||||
"fps": ("FLOAT", {"default": 24.0, "min": 0.01, "max": 1000.0, "step": 0.01}),
|
"fps": ("FLOAT", {"default": 24.0, "min": 0.01, "max": 1000.0, "step": 0.01}),
|
||||||
"lossless": ("BOOLEAN", {"default": False}),
|
# "lossless": ("BOOLEAN", {"default": False}),
|
||||||
"quality": ("INT", {"default": 90, "min": 0, "max": 100}),
|
# "quality": ("INT", {"default": 90, "min": 0, "max": 100}),
|
||||||
"method": (list(s.methods.keys()),),
|
# "method": (list(s.methods.keys()),),
|
||||||
"crf": ("FLOAT",),
|
"crf": ("FLOAT",),
|
||||||
"motion_prompt": ("STRING", ),
|
"motion_prompt": ("STRING", ),
|
||||||
"negative_prompt": ("STRING", ),
|
"negative_prompt": ("STRING", ),
|
||||||
@ -56,9 +60,8 @@ class MD_SaveAnimatedWEBP:
|
|||||||
|
|
||||||
CATEGORY = "MemeDeck"
|
CATEGORY = "MemeDeck"
|
||||||
|
|
||||||
def save_images(self, images, fps, filename_prefix, lossless, quality, method, crf=None, motion_prompt=None, negative_prompt=None, img2vid_metadata=None, sampler_metadata=None):
|
def save_images(self, images, fps, filename_prefix, crf=None, motion_prompt=None, negative_prompt=None, img2vid_metadata=None, sampler_metadata=None):
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
method = self.methods.get(method)
|
|
||||||
filename_prefix += self.prefix_append
|
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])
|
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 = []
|
results = []
|
||||||
@ -67,15 +70,18 @@ class MD_SaveAnimatedWEBP:
|
|||||||
pil_images = [Image.fromarray(np.clip(255. * image.cpu().numpy(), 0, 255).astype(np.uint8)) for image in images]
|
pil_images = [Image.fromarray(np.clip(255. * image.cpu().numpy(), 0, 255).astype(np.uint8)) for image in images]
|
||||||
|
|
||||||
first_image = pil_images[0]
|
first_image = pil_images[0]
|
||||||
|
width = first_image.width
|
||||||
|
height = first_image.height
|
||||||
|
|
||||||
padding = 8
|
padding = 8
|
||||||
x = first_image.width - WATERMARK_SIZE - padding
|
x = width - WATERMARK_SIZE - padding
|
||||||
y = first_image.height - WATERMARK_SIZE - padding
|
y = height - WATERMARK_SIZE - padding
|
||||||
first_image_background_brightness = self.analyze_background_brightness(first_image, x, y, WATERMARK_SIZE)
|
first_image_background_brightness = self.analyze_background_brightness(first_image, x, y, WATERMARK_SIZE)
|
||||||
|
|
||||||
watermarked_images = [self.add_watermark_to_image(img, first_image_background_brightness) for img in pil_images]
|
watermarked_images = [self.add_watermark_to_image(img, first_image_background_brightness) for img in pil_images]
|
||||||
|
|
||||||
metadata = pil_images[0].getexif()
|
# metadata = pil_images[0].getexif()
|
||||||
num_frames = len(pil_images)
|
# num_frames = len(pil_images)
|
||||||
|
|
||||||
json_metadata = {
|
json_metadata = {
|
||||||
"crf": crf,
|
"crf": crf,
|
||||||
@ -85,96 +91,61 @@ class MD_SaveAnimatedWEBP:
|
|||||||
"sampler_metadata": json.loads(sampler_metadata),
|
"sampler_metadata": json.loads(sampler_metadata),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Optimized saving logic
|
# Use ffmpeg to create MP4 with watermark
|
||||||
if num_frames == 1: # Single image, save once
|
output_file = f"{filename}_{counter:05}_.mp4"
|
||||||
file = f"{filename}_{counter:05}_.webp"
|
output_path = os.path.join(full_output_folder, output_file)
|
||||||
watermarked_images[0].save(os.path.join(full_output_folder, file), exif=metadata, lossless=lossless, quality=quality, method=method)
|
ffmpeg_cmd = [
|
||||||
results.append({
|
utils.ffmpeg_path,
|
||||||
"filename": file,
|
"-v", "error",
|
||||||
"subfolder": subfolder,
|
'-f', 'rawvideo',
|
||||||
"type": self.type,
|
'-pix_fmt', 'rgb24',
|
||||||
})
|
"-r", str(fps), # Set frame rate
|
||||||
else: # multiple images, save as animation
|
"-s", f"{width}x{height}",
|
||||||
file = f"{filename}_{counter:05}_.webp"
|
"-i", "-",
|
||||||
watermarked_images[0].save(os.path.join(full_output_folder, file), save_all=True, duration=int(1000.0 / fps), append_images=watermarked_images[1:], exif=metadata, lossless=lossless, quality=quality, method=method)
|
"-y", # Overwrite output file if it exists
|
||||||
results.append({
|
"-c:v", "libx264", # Use x264 encoder
|
||||||
"filename": file,
|
"-crf", "19", # Set CRF (quality)
|
||||||
"subfolder": subfolder,
|
"-pix_fmt", "yuv420p", # Set pixel format
|
||||||
"type": self.type,
|
]
|
||||||
})
|
|
||||||
|
|
||||||
animated = num_frames != 1
|
env = os.environ.copy()
|
||||||
|
output_process = ffmpeg_process(ffmpeg_cmd, output_path, env)
|
||||||
|
|
||||||
|
# Proceed to first yield
|
||||||
|
output_process.send(None)
|
||||||
|
output_process.send(first_image.tobytes())
|
||||||
|
for image in watermarked_images:
|
||||||
|
output_process.send(image.tobytes())
|
||||||
|
try:
|
||||||
|
output_process.send(None) # Signal end of input
|
||||||
|
next(output_process) # Get the final yield
|
||||||
|
except StopIteration:
|
||||||
|
pass
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
"filename": output_file,
|
||||||
|
"subfolder": subfolder,
|
||||||
|
"type": self.type,
|
||||||
|
})
|
||||||
|
|
||||||
end_time = time.time()
|
end_time = time.time()
|
||||||
logger.info(f"Save images took: {end_time - start_time} seconds")
|
logger.info(f"Save images took: {end_time - start_time} seconds")
|
||||||
|
|
||||||
return {
|
preview = {
|
||||||
"ui": {
|
"filename": output_path,
|
||||||
"images": results,
|
"subfolder": subfolder,
|
||||||
"animated": (animated,),
|
"type": "output",
|
||||||
"metadata": (json.dumps(json_metadata),)
|
"format": "video/mp4",
|
||||||
},
|
"frame_rate": fps,
|
||||||
}
|
}
|
||||||
|
|
||||||
# def save_images(self, images, fps, filename_prefix, lossless, quality, method, crf=None, motion_prompt=None, negative_prompt=None, img2vid_metadata=None, sampler_metadata=None):
|
return {
|
||||||
# start_time = time.time()
|
"ui": {
|
||||||
# method = self.methods.get(method)
|
"gifs": [preview],
|
||||||
# filename_prefix += self.prefix_append
|
"metadata": (json.dumps(json_metadata),)
|
||||||
# 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 = []
|
"result": ((True, [output_path]),)
|
||||||
|
}
|
||||||
# # Vectorized conversion to PIL images
|
|
||||||
# pil_images = [Image.fromarray(np.clip(255. * image.cpu().numpy(), 0, 255).astype(np.uint8)) for image in images]
|
|
||||||
|
|
||||||
# first_image = pil_images[0]
|
|
||||||
# padding = 12
|
|
||||||
# x = first_image.width - WATERMARK_SIZE - padding
|
|
||||||
# y = first_image.height - WATERMARK_SIZE - padding
|
|
||||||
# first_image_background_brightness = self.analyze_background_brightness(first_image, x, y, WATERMARK_SIZE)
|
|
||||||
|
|
||||||
# watermarked_images = [self.add_watermark_to_image(img, first_image_background_brightness) for img in pil_images]
|
|
||||||
|
|
||||||
# metadata = pil_images[0].getexif()
|
|
||||||
# num_frames = len(pil_images)
|
|
||||||
|
|
||||||
# json_metadata = {
|
|
||||||
# "crf": crf,
|
|
||||||
# "motion_prompt": motion_prompt,
|
|
||||||
# "negative_prompt": negative_prompt,
|
|
||||||
# "img2vid_metadata": json.loads(img2vid_metadata),
|
|
||||||
# "sampler_metadata": json.loads(sampler_metadata),
|
|
||||||
# }
|
|
||||||
|
|
||||||
# # Optimized saving logic
|
|
||||||
# if num_frames == 1: # Single image, save once
|
|
||||||
# file = f"{filename}_{counter:05}_.webp"
|
|
||||||
# watermarked_images[0].save(os.path.join(full_output_folder, file), exif=metadata, lossless=lossless, quality=quality, method=method)
|
|
||||||
# results.append({
|
|
||||||
# "filename": file,
|
|
||||||
# "subfolder": subfolder,
|
|
||||||
# "type": self.type,
|
|
||||||
# })
|
|
||||||
# else: # multiple images, save as animation
|
|
||||||
# file = f"{filename}_{counter:05}_.webp"
|
|
||||||
# watermarked_images[0].save(os.path.join(full_output_folder, file), save_all=True, duration=int(1000.0 / fps), append_images=watermarked_images[1:], exif=metadata, lossless=lossless, quality=quality, method=method)
|
|
||||||
# results.append({
|
|
||||||
# "filename": file,
|
|
||||||
# "subfolder": subfolder,
|
|
||||||
# "type": self.type,
|
|
||||||
# })
|
|
||||||
|
|
||||||
# animated = num_frames != 1
|
|
||||||
|
|
||||||
# end_time = time.time()
|
|
||||||
# logger.info(f"Save images took: {end_time - start_time} seconds")
|
|
||||||
|
|
||||||
# return {
|
|
||||||
# "ui": {
|
|
||||||
# "images": results,
|
|
||||||
# "animated": (animated,),
|
|
||||||
# "metadata": (json.dumps(json_metadata),)
|
|
||||||
# },
|
|
||||||
# }
|
|
||||||
|
|
||||||
def add_watermark_to_image(self, img, background_brightness=None):
|
def add_watermark_to_image(self, img, background_brightness=None):
|
||||||
"""
|
"""
|
||||||
@ -402,63 +373,63 @@ class MD_VAEDecode:
|
|||||||
return (images,)
|
return (images,)
|
||||||
|
|
||||||
|
|
||||||
class MD_SaveMP4:
|
# class MD_SaveMP4:
|
||||||
def __init__(self):
|
# def __init__(self):
|
||||||
# Get absolute path of the output directory
|
# # Get absolute path of the output directory
|
||||||
self.output_dir = os.path.abspath("output/video_gen")
|
# self.output_dir = os.path.abspath("output/video_gen")
|
||||||
self.type = "output"
|
# self.type = "output"
|
||||||
self.prefix_append = ""
|
# self.prefix_append = ""
|
||||||
|
|
||||||
methods = {"default": 4, "fastest": 0, "slowest": 6}
|
# methods = {"default": 4, "fastest": 0, "slowest": 6}
|
||||||
|
|
||||||
@classmethod
|
# @classmethod
|
||||||
def INPUT_TYPES(s):
|
# def INPUT_TYPES(s):
|
||||||
return {"required":
|
# return {"required":
|
||||||
{"images": ("IMAGE", ),
|
# {"images": ("IMAGE", ),
|
||||||
"filename_prefix": ("STRING", {"default": "ComfyUI"}),
|
# "filename_prefix": ("STRING", {"default": "ComfyUI"}),
|
||||||
"fps": ("FLOAT", {"default": 24.0, "min": 0.01, "max": 1000.0, "step": 0.01}),
|
# "fps": ("FLOAT", {"default": 24.0, "min": 0.01, "max": 1000.0, "step": 0.01}),
|
||||||
"quality": ("INT", {"default": 80, "min": 0, "max": 100}),
|
# "quality": ("INT", {"default": 80, "min": 0, "max": 100}),
|
||||||
},
|
# },
|
||||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
# "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||||
}
|
# }
|
||||||
|
|
||||||
RETURN_TYPES = ()
|
# RETURN_TYPES = ()
|
||||||
FUNCTION = "save_video"
|
# FUNCTION = "save_video"
|
||||||
|
|
||||||
OUTPUT_NODE = True
|
# OUTPUT_NODE = True
|
||||||
|
|
||||||
CATEGORY = "MemeDeck"
|
# CATEGORY = "MemeDeck"
|
||||||
|
|
||||||
def save_video(self, images, fps, filename_prefix, quality, prompt=None, extra_pnginfo=None):
|
# def save_video(self, images, fps, filename_prefix, quality, prompt=None, extra_pnginfo=None):
|
||||||
filename_prefix += self.prefix_append
|
# filename_prefix += self.prefix_append
|
||||||
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(
|
# 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]
|
# filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0]
|
||||||
)
|
# )
|
||||||
results = list()
|
# results = list()
|
||||||
video_path = os.path.join(full_output_folder, f"{filename}_{counter:05}.mp4")
|
# video_path = os.path.join(full_output_folder, f"{filename}_{counter:05}.mp4")
|
||||||
|
|
||||||
# Determine video resolution
|
# # Determine video resolution
|
||||||
height, width = images[0].shape[1], images[0].shape[2]
|
# height, width = images[0].shape[1], images[0].shape[2]
|
||||||
video_writer = cv2.VideoWriter(
|
# video_writer = cv2.VideoWriter(
|
||||||
video_path,
|
# video_path,
|
||||||
cv2.VideoWriter_fourcc(*'mp4v'),
|
# cv2.VideoWriter_fourcc(*'mp4v'),
|
||||||
fps,
|
# fps,
|
||||||
(width, height)
|
# (width, height)
|
||||||
)
|
# )
|
||||||
|
|
||||||
# Write each frame to the video
|
# # Write each frame to the video
|
||||||
for image in images:
|
# for image in images:
|
||||||
i = 255. * image.cpu().numpy()
|
# i = 255. * image.cpu().numpy()
|
||||||
frame = np.clip(i, 0, 255).astype(np.uint8)
|
# frame = np.clip(i, 0, 255).astype(np.uint8)
|
||||||
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) # Convert RGB to BGR for OpenCV
|
# frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) # Convert RGB to BGR for OpenCV
|
||||||
video_writer.write(frame)
|
# video_writer.write(frame)
|
||||||
|
|
||||||
video_writer.release()
|
# video_writer.release()
|
||||||
|
|
||||||
results.append({
|
# results.append({
|
||||||
"filename": os.path.basename(video_path),
|
# "filename": os.path.basename(video_path),
|
||||||
"subfolder": subfolder,
|
# "subfolder": subfolder,
|
||||||
"type": self.type
|
# "type": self.type
|
||||||
})
|
# })
|
||||||
|
|
||||||
return {"ui": {"videos": results}}
|
# return {"ui": {"videos": results}}
|
@ -23,6 +23,29 @@ import logging
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logger.setLevel(logging.INFO)
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
def ffmpeg_process(args, file_path, env):
|
||||||
|
res = None
|
||||||
|
frame_data = yield
|
||||||
|
total_frames_output = 0
|
||||||
|
if res != b'':
|
||||||
|
with subprocess.Popen(args + [file_path], stderr=subprocess.PIPE,
|
||||||
|
stdin=subprocess.PIPE, env=env) as proc:
|
||||||
|
try:
|
||||||
|
while frame_data is not None:
|
||||||
|
proc.stdin.write(frame_data)
|
||||||
|
frame_data = yield
|
||||||
|
total_frames_output+=1
|
||||||
|
proc.stdin.flush()
|
||||||
|
proc.stdin.close()
|
||||||
|
res = proc.stderr.read()
|
||||||
|
except BrokenPipeError as e:
|
||||||
|
res = proc.stderr.read()
|
||||||
|
raise Exception("An error occurred in the ffmpeg subprocess:\n" \
|
||||||
|
+ res.decode("utf-8"))
|
||||||
|
yield total_frames_output
|
||||||
|
if len(res) > 0:
|
||||||
|
print(res.decode("utf-8"), end="", file=sys.stderr)
|
||||||
|
|
||||||
class MD_LoadImageFromUrl:
|
class MD_LoadImageFromUrl:
|
||||||
"""Load an image from the given URL"""
|
"""Load an image from the given URL"""
|
||||||
|
|
||||||
@ -66,6 +89,13 @@ class MD_ImageToMotionPrompt:
|
|||||||
"default": "masterpiece, 4k, HDR, cinematic,",
|
"default": "masterpiece, 4k, HDR, cinematic,",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
"post_prompt": (
|
||||||
|
"STRING",
|
||||||
|
{
|
||||||
|
"multiline": False,
|
||||||
|
"default": "The scene appears to be from a movie or TV show.",
|
||||||
|
},
|
||||||
|
),
|
||||||
"prompt": (
|
"prompt": (
|
||||||
"STRING",
|
"STRING",
|
||||||
{
|
{
|
||||||
@ -91,7 +121,7 @@ class MD_ImageToMotionPrompt:
|
|||||||
CATEGORY = "MemeDeck"
|
CATEGORY = "MemeDeck"
|
||||||
|
|
||||||
def generate_completion(
|
def generate_completion(
|
||||||
self, pre_prompt: str, Image: torch.Tensor, clip, prompt: str, negative_prompt: str, max_tokens: int
|
self, pre_prompt: str, post_prompt: str, Image: torch.Tensor, clip, prompt: str, negative_prompt: str, max_tokens: int
|
||||||
) -> Tuple[str]:
|
) -> Tuple[str]:
|
||||||
# start a timer
|
# start a timer
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
@ -104,7 +134,7 @@ class MD_ImageToMotionPrompt:
|
|||||||
end_time = time.time()
|
end_time = time.time()
|
||||||
|
|
||||||
logger.info(f"Motion prompt took: {end_time - start_time} seconds")
|
logger.info(f"Motion prompt took: {end_time - start_time} seconds")
|
||||||
full_prompt = f"{pre_prompt}\n{response.json()['result']}"
|
full_prompt = f"{pre_prompt}\n{response.json()['result']} {post_prompt}"
|
||||||
|
|
||||||
pos_tokens = clip.tokenize(full_prompt)
|
pos_tokens = clip.tokenize(full_prompt)
|
||||||
pos_output = clip.encode_from_tokens(pos_tokens, return_pooled=True, return_dict=True)
|
pos_output = clip.encode_from_tokens(pos_tokens, return_pooled=True, return_dict=True)
|
||||||
@ -166,29 +196,6 @@ class MD_CompressAdjustNode:
|
|||||||
def tensor_to_bytes(self, tensor):
|
def tensor_to_bytes(self, tensor):
|
||||||
return self.tensor_to_int(tensor, 8).astype(np.uint8)
|
return self.tensor_to_int(tensor, 8).astype(np.uint8)
|
||||||
|
|
||||||
def ffmpeg_process(self, args, file_path, env):
|
|
||||||
res = None
|
|
||||||
frame_data = yield
|
|
||||||
total_frames_output = 0
|
|
||||||
if res != b'':
|
|
||||||
with subprocess.Popen(args + [file_path], stderr=subprocess.PIPE,
|
|
||||||
stdin=subprocess.PIPE, env=env) as proc:
|
|
||||||
try:
|
|
||||||
while frame_data is not None:
|
|
||||||
proc.stdin.write(frame_data)
|
|
||||||
frame_data = yield
|
|
||||||
total_frames_output+=1
|
|
||||||
proc.stdin.flush()
|
|
||||||
proc.stdin.close()
|
|
||||||
res = proc.stderr.read()
|
|
||||||
except BrokenPipeError as e:
|
|
||||||
res = proc.stderr.read()
|
|
||||||
raise Exception("An error occurred in the ffmpeg subprocess:\n" \
|
|
||||||
+ res.decode("utf-8"))
|
|
||||||
yield total_frames_output
|
|
||||||
if len(res) > 0:
|
|
||||||
print(res.decode("utf-8"), end="", file=sys.stderr)
|
|
||||||
|
|
||||||
def detect_image_clarity(self, image):
|
def detect_image_clarity(self, image):
|
||||||
# detect the clarity of the image
|
# detect the clarity of the image
|
||||||
# return a score between 0 and 100
|
# return a score between 0 and 100
|
||||||
@ -286,7 +293,7 @@ class MD_CompressAdjustNode:
|
|||||||
i_pix_fmt = 'rgb24'
|
i_pix_fmt = 'rgb24'
|
||||||
|
|
||||||
# default bitrate and frame rate
|
# default bitrate and frame rate
|
||||||
frame_rate = 25
|
frame_rate = 24
|
||||||
|
|
||||||
image_cv2 = cv2.cvtColor(np.array(tensor2pil(image)), cv2.COLOR_RGB2BGR)
|
image_cv2 = cv2.cvtColor(np.array(tensor2pil(image)), cv2.COLOR_RGB2BGR)
|
||||||
# calculate the crf based on the image
|
# calculate the crf based on the image
|
||||||
@ -295,7 +302,11 @@ class MD_CompressAdjustNode:
|
|||||||
self.ideal_color_variation, self.blockiness_weight,
|
self.ideal_color_variation, self.blockiness_weight,
|
||||||
self.edge_density_weight, self.color_variation_weight)
|
self.edge_density_weight, self.color_variation_weight)
|
||||||
|
|
||||||
logger.info(f"detected crf: {calculated_crf}")
|
if desired_crf is 0:
|
||||||
|
desired_crf = calculated_crf
|
||||||
|
|
||||||
|
logger.info(f"calculated_crf: {calculated_crf}")
|
||||||
|
logger.info(f"desired_crf: {desired_crf}")
|
||||||
args = [
|
args = [
|
||||||
utils.ffmpeg_path,
|
utils.ffmpeg_path,
|
||||||
"-v", "error",
|
"-v", "error",
|
||||||
@ -312,7 +323,7 @@ class MD_CompressAdjustNode:
|
|||||||
|
|
||||||
video_path = os.path.abspath(str(Path(temp_dir) / f"{filename}.mp4"))
|
video_path = os.path.abspath(str(Path(temp_dir) / f"{filename}.mp4"))
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
output_process = self.ffmpeg_process(args, video_path, env)
|
output_process = ffmpeg_process(args, video_path, env)
|
||||||
|
|
||||||
# Proceed to first yield
|
# Proceed to first yield
|
||||||
output_process.send(None)
|
output_process.send(None)
|
||||||
|
@ -4,3 +4,7 @@ torch
|
|||||||
Pillow
|
Pillow
|
||||||
opencv-python
|
opencv-python
|
||||||
torchvision
|
torchvision
|
||||||
|
lxml
|
||||||
|
cairosvg
|
||||||
|
|
||||||
|
ltx-video@git+https://github.com/Lightricks/LTX-Video@ltx-video-0.9.1
|
||||||
|
15
memedeck.py
15
memedeck.py
@ -414,23 +414,24 @@ class MemedeckWorker:
|
|||||||
|
|
||||||
if event == "executed":
|
if event == "executed":
|
||||||
if data['node'] == task['end_node_id']:
|
if data['node'] == task['end_node_id']:
|
||||||
filename = data['output']['images'][0]['filename']
|
# self.logger.info(f"[memedeck]: video gen completed {data}")
|
||||||
|
file_path = data['output']['gifs'][0]['filename']
|
||||||
metadata = json.loads(data['output']['metadata'][0])
|
metadata = json.loads(data['output']['metadata'][0])
|
||||||
|
|
||||||
self.logger.info(f"[memedeck]: video gen completed {metadata}")
|
self.logger.info(f"[memedeck]: video gen completed {metadata}")
|
||||||
|
|
||||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
# current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
file_path = os.path.join(current_dir, "output", filename)
|
# file_path = os.path.join(current_dir, "output", filename)
|
||||||
blob_name = f"{task['user_id']}/video_gen/video_{task['image_id'].replace('image:', '')}_{task['prompt_id']}.webp"
|
blob_name = f"{task['user_id']}/video_gen/video_{task['image_id'].replace('image:', '')}_{task['prompt_id']}.mp4"
|
||||||
|
|
||||||
# TODO: take the file path and upload to azure blob storage
|
# TODO: take the file path and upload to azure blob storage
|
||||||
# load image bytes
|
# load image bytes
|
||||||
with open(file_path, "rb") as image_file:
|
with open(file_path, "rb") as video_file:
|
||||||
image_bytes = image_file.read()
|
video_bytes = video_file.read()
|
||||||
|
|
||||||
self.logger.info(f"[memedeck]: video gen completed for {sid}, file={file_path}, blob={blob_name}")
|
self.logger.info(f"[memedeck]: video gen completed for {sid}, file={file_path}, blob={blob_name}")
|
||||||
|
|
||||||
url = await self.azure_storage.save_image(blob_name, "image/webp", image_bytes)
|
url = await self.azure_storage.save_image(blob_name, "video/mp4", video_bytes)
|
||||||
|
|
||||||
self.logger.info(f"[memedeck]: video gen completed for {sid}, {url}")
|
self.logger.info(f"[memedeck]: video gen completed for {sid}, {url}")
|
||||||
await self.send_to_api({
|
await self.send_to_api({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user