Files
TashaSkyUp-EternalKernelPyt…/video.py
T
2023-05-30 15:04:02 -04:00

247 lines
8.2 KiB
Python

### info for code completion AI ###
"""
all of these classes are plugins for comfyui and follow the same pattern
all of the images are torch tensors and it is unknown and unimportant if they are on the cpu or gpu
all image inputs are (B,W,H,C)
avoid numpy and PIL as much as possible
"""
import os
from abc import ABC, ABCMeta, abstractmethod
from typing import List, Tuple, Union
import cv2
import torch
from cv2 import VideoWriter, VideoWriter_fourcc, VideoCapture
from torch import dtype
NODE_CLASS_MAPPINGS = {} # this is the dictionary that will be used to register the nodes
# setup directories
root_p = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
input_dir = os.path.join(root_p, 'input', 'video')
output_dir = os.path.join(root_p, 'output', 'video')
if not os.path.exists(input_dir):
os.makedirs(input_dir)
if not os.path.exists(output_dir):
os.makedirs(output_dir)
out_video_folders = lambda s: sorted(
[name for name in os.listdir(s.output_dir) if os.path.isdir(os.path.join(s.output_dir, name))])
in_video_folders = lambda s: sorted(
[name for name in os.listdir(s.input_dir) if os.path.isdir(os.path.join(s.input_dir, name))])
# any files in in_video_folders ending in .mp4
in_video_files = lambda s: sorted(
[name for name in os.listdir(s.input_dir) if
os.path.isfile(os.path.join(s.input_dir, name)) and name.endswith('.mp4')])
# start with an ABC to define the common widget interface
class WidgetMetaclass(ABCMeta):
"""A metaclass that automatically registers classes."""
def __init__(cls, name, bases, attrs):
if (ABC in bases) or (VideoWidget in bases):
pass
else:
NODE_CLASS_MAPPINGS[name] = cls
super().__init__(name, bases, attrs)
class VideoWidget(ABC, metaclass=WidgetMetaclass):
"""Abstract base class for simple construction"""
input_dir = input_dir
output_dir = output_dir
CATEGORY = "video"
RETURN_TYPES = ("VIDEO",)
FUNCTION = "handler"
@classmethod
@abstractmethod
def INPUT_TYPES(cls):
"""All subclasses must provide a INPUT_TYPES classmethod"""
pass
@abstractmethod
def handler(self, one: object, two: object):
"""All subclasses must provide a handler method"""
pass
class VideoFileToVideoFolder(VideoWidget, metaclass=WidgetMetaclass):
"""Abstract base class for simple construction"""
@classmethod
def INPUT_TYPES(cls):
return {"required":
{
"video_in": (in_video_files(cls),),
"folder_out": (out_video_folders(cls),),
},
"optional":
{"text":
("STRING", {"multiline": False}),
}
}
@abstractmethod
def handler(self, one, two, text):
return (one, two,)
class VideoFolderToImage(VideoWidget, metaclass=WidgetMetaclass):
"""Abstract base class for simple construction"""
RETURN_TYPES = ("IMAGE",)
@classmethod
def INPUT_TYPES(cls):
return {"required":
{
"folder_in": (in_video_folders(cls),),
"idx_slice_start": ("INT", {"min": 0, "max": 10000, "step": 1, "default": 0}),
"idx_slice_stop": ("INT", {"min": 0, "max": 10000, "step": 1, "default": 1}),
"slice_idx_step": ("INT", {"min": 0, "max": 1000, "step": 1, "default": 1}),
"idx": ("INT", {"min": 0, "max": 10000, "step": 1, "default": 0}),
},
"optional":
{"text":
("STRING", {"multiline": False}),
}
}
@abstractmethod
def handler(self, one, two, text):
return (one,)
class VideoFileToImage(VideoWidget, metaclass=WidgetMetaclass):
"""Abstract base class for simple construction"""
RETURN_TYPES = ("IMAGE",)
@classmethod
def INPUT_TYPES(cls):
return {"required":
{
"video_in": (in_video_files(cls),),
"idx_slice_start": ("INT", {"min": 0, "max": 10000, "step": 1, "default": 0}),
"idx_slice_stop": ("INT", {"min": 0, "max": 10000, "step": 1, "default": 1}),
"slice_idx_step": ("INT", {"min": 0, "max": 1000, "step": 1, "default": 1}),
"idx": ("INT", {"min": 0, "max": 10000, "step": 1, "default": 0}),
},
"optional":
{"text":
("STRING", {"multiline": False}),
}
}
@abstractmethod
def handler(self, one, two, text):
return (one,)
## end of abstract base classes ##
class VideoToFrames(VideoFileToVideoFolder, metaclass=WidgetMetaclass):
"""use cv2 to open the video and use it as a source for image stacks"""
RETURN_TYPES = ("IMAGE",)
def __init__(self):
super().__init__()
self.old = self.INPUT_TYPES
# self.INPUT_TYPES = lambda s: self.old().update({})
def handler(self, video_in, folder_out, text):
video_in_path = os.path.join(self.input_dir, video_in)
video_out_path = os.path.join(self.output_dir, folder_out)
vidcap = cv2.VideoCapture(video_in_path)
success, image = vidcap.read()
count = 0
while success:
cv2.imwrite(os.path.join(video_out_path, "frame%d.jpg" % count), image)
success, image = vidcap.read()
count += 1
return (folder_out,)
class VideoFramesToImageStack(VideoFolderToImage, metaclass=WidgetMetaclass):
"""Use cv2 to open the video and save the videos individual frames"""
def handler(self, folder_in, text, idx_slice_start, idx_slice_stop, slice_idx_step, idx):
"""
Return slices of time from the video, sequences of images, defined by the start, stop and step
- torch tensors (T,H,W,C)
"""
import glob
folder_in_path = os.path.join(self.input_dir, folder_in)
image_list = []
# List and sort all PNG or JPG files in the folder
file_fps = sorted(glob.glob(os.path.join(folder_in_path, '*.png')) + glob.glob(os.path.join(folder_in_path, '*.jpg')))
# Ensure the slice doesn't go beyond the end of the file list
if idx_slice_stop+idx > len(file_fps):
idx_slice_stop = len(file_fps) - idx
for i in range(idx_slice_start+idx, idx_slice_stop+idx, slice_idx_step):
for fn in file_fps[i:i+slice_idx_step]:
image = cv2.imread(fn)
if image is not None:
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # converting to RGB format
image_list.append(image)
# Convert list of images to tensor (T,H,W,C)
tensor = torch.stack([torch.tensor(img,dtype=torch.float32) for img in image_list])
tensor=tensor/255.0
return (tensor,)
class VideoFileToImageStack(VideoFileToImage, metaclass=WidgetMetaclass):
"""Use cv2 to open a video and save the videos individual frames"""
def handler(self, video_in, text, idx_slice_start, idx_slice_stop, slice_idx_step, idx):
"""
Return slices of time from the video, sequences of images, defined by the start, stop and step
- torch tensors (T,H,W,C)
"""
image_list = []
video_in_fp = os.path.join(self.input_dir, video_in)
vidcap = cv2.VideoCapture(video_in_fp)
if not vidcap.isOpened():
print("Error: Unable to open video file")
return
start = (idx * slice_idx_step) + idx_slice_start
stop = (idx * slice_idx_step) + idx_slice_stop
for frame_count in range(start, stop, slice_idx_step):
# Seek to frame_count frame
vidcap.set(cv2.CAP_PROP_POS_FRAMES, frame_count)
success, image = vidcap.read()
if not success:
print("Error: Unable to read frame from video file")
break
#image_list.append(image)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image_list.append(image)
# Convert list of images to tensor (T,H,W,C)
tensor = torch.stack([torch.tensor(img, dtype=torch.float32) for img in image_list])
tensor = tensor / 255.0
return (tensor,)
if __name__ == "__main__":
print(NODE_CLASS_MAPPINGS)