mirror of
https://github.com/storytold/storyteller-ml.git
synced 2026-10-15 00:03:06 +00:00
more extensions
This commit is contained in:
+11
-9
@@ -3,6 +3,8 @@ import torch
|
||||
|
||||
import comfy.utils
|
||||
|
||||
from .utils import BIGMIN, BIGMAX
|
||||
|
||||
|
||||
class MergeStrategies:
|
||||
MATCH_A = "match A"
|
||||
@@ -36,7 +38,7 @@ class SplitLatents:
|
||||
return {
|
||||
"required": {
|
||||
"latents": ("LATENT",),
|
||||
"split_index": ("INT", {"default": 0, "step": 1, "min": -99999999999}),
|
||||
"split_index": ("INT", {"default": 0, "step": 1, "min": BIGMIN, "max": BIGMAX}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -61,7 +63,7 @@ class SplitImages:
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"split_index": ("INT", {"default": 0, "step": 1, "min": -99999999999}),
|
||||
"split_index": ("INT", {"default": 0, "step": 1, "min": BIGMIN, "max": BIGMAX}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -83,7 +85,7 @@ class SplitMasks:
|
||||
return {
|
||||
"required": {
|
||||
"mask": ("MASK",),
|
||||
"split_index": ("INT", {"default": 0, "step": 1, "min": -99999999999}),
|
||||
"split_index": ("INT", {"default": 0, "step": 1, "min": BIGMIN, "max": BIGMAX}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -258,7 +260,7 @@ class SelectEveryNthLatent:
|
||||
return {
|
||||
"required": {
|
||||
"latents": ("LATENT",),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "max": BIGMAX, "step": 1}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -279,7 +281,7 @@ class SelectEveryNthImage:
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "max": BIGMAX, "step": 1}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -300,7 +302,7 @@ class SelectEveryNthMask:
|
||||
return {
|
||||
"required": {
|
||||
"mask": ("MASK",),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "max": BIGMAX, "step": 1}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -378,7 +380,7 @@ class DuplicateLatents:
|
||||
return {
|
||||
"required": {
|
||||
"latents": ("LATENT",),
|
||||
"multiply_by": ("INT", {"default": 1, "min": 1, "step": 1})
|
||||
"multiply_by": ("INT", {"default": 1, "min": 1, "max": BIGMAX, "step": 1})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,7 +405,7 @@ class DuplicateImages:
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"multiply_by": ("INT", {"default": 1, "min": 1, "step": 1})
|
||||
"multiply_by": ("INT", {"default": 1, "min": 1, "max": BIGMAX, "step": 1})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,7 +429,7 @@ class DuplicateMasks:
|
||||
return {
|
||||
"required": {
|
||||
"mask": ("MASK",),
|
||||
"multiply_by": ("INT", {"default": 1, "min": 1, "step": 1})
|
||||
"multiply_by": ("INT", {"default": 1, "min": 1, "max": BIGMAX, "step": 1})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-7
@@ -7,7 +7,7 @@ from PIL import Image, ImageOps
|
||||
import folder_paths
|
||||
from comfy.k_diffusion.utils import FolderOfImages
|
||||
from .logger import logger
|
||||
from .utils import calculate_file_hash, get_sorted_dir_files_from_directory, validate_path
|
||||
from .utils import BIGMAX, calculate_file_hash, get_sorted_dir_files_from_directory, validate_path
|
||||
|
||||
|
||||
def is_changed_load_images(directory: str, image_load_cap: int = 0, skip_first_images: int = 0, select_every_nth: int = 1):
|
||||
@@ -93,9 +93,9 @@ class LoadImagesFromDirectoryUpload:
|
||||
"directory": (directories,),
|
||||
},
|
||||
"optional": {
|
||||
"image_load_cap": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"skip_first_images": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "step": 1}),
|
||||
"image_load_cap": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}),
|
||||
"skip_first_images": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "max": BIGMAX, "step": 1}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,9 +127,9 @@ class LoadImagesFromDirectoryPath:
|
||||
"directory": ("STRING", {"default": "X://path/to/images", "vhs_path_extensions": []}),
|
||||
},
|
||||
"optional": {
|
||||
"image_load_cap": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"skip_first_images": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "step": 1}),
|
||||
"image_load_cap": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}),
|
||||
"skip_first_images": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "max": BIGMAX, "step": 1}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-13
@@ -8,7 +8,7 @@ import cv2
|
||||
import folder_paths
|
||||
from comfy.utils import common_upscale
|
||||
from .logger import logger
|
||||
from .utils import calculate_file_hash, get_sorted_dir_files_from_directory, get_audio, lazy_eval, hash_path, validate_path
|
||||
from .utils import BIGMAX, DIMMAX, calculate_file_hash, get_sorted_dir_files_from_directory, get_audio, lazy_eval, hash_path, validate_path
|
||||
|
||||
|
||||
video_extensions = ['webm', 'mp4', 'mkv', 'gif']
|
||||
@@ -156,13 +156,13 @@ class LoadVideoUpload:
|
||||
files.append(f)
|
||||
return {"required": {
|
||||
"video": (sorted(files),),
|
||||
"force_rate": ("INT", {"default": 0, "min": 0, "max": 24, "step": 1}),
|
||||
"force_rate": ("INT", {"default": 0, "min": 0, "max": 60, "step": 1}),
|
||||
"force_size": (["Disabled", "Custom Height", "Custom Width", "Custom", "256x?", "?x256", "256x256", "512x?", "?x512", "512x512"],),
|
||||
"custom_width": ("INT", {"default": 512, "min": 0, "step": 8}),
|
||||
"custom_height": ("INT", {"default": 512, "min": 0, "step": 8}),
|
||||
"frame_load_cap": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"skip_first_frames": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "step": 1}),
|
||||
"custom_width": ("INT", {"default": 512, "min": 0, "max": DIMMAX, "step": 8}),
|
||||
"custom_height": ("INT", {"default": 512, "min": 0, "max": DIMMAX, "step": 8}),
|
||||
"frame_load_cap": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}),
|
||||
"skip_first_frames": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "max": BIGMAX, "step": 1}),
|
||||
},
|
||||
"optional": {
|
||||
"batch_manager": ("VHS_BatchManager",)
|
||||
@@ -200,13 +200,13 @@ class LoadVideoPath:
|
||||
return {
|
||||
"required": {
|
||||
"video": ("STRING", {"default": "X://insert/path/here.mp4", "vhs_path_extensions": video_extensions}),
|
||||
"force_rate": ("INT", {"default": 0, "min": 0, "max": 24, "step": 1}),
|
||||
"force_rate": ("INT", {"default": 0, "min": 0, "max": 60, "step": 1}),
|
||||
"force_size": (["Disabled", "Custom Height", "Custom Width", "Custom", "256x?", "?x256", "256x256", "512x?", "?x512", "512x512"],),
|
||||
"custom_width": ("INT", {"default": 512, "min": 0, "step": 8}),
|
||||
"custom_height": ("INT", {"default": 512, "min": 0, "step": 8}),
|
||||
"frame_load_cap": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"skip_first_frames": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "step": 1}),
|
||||
"custom_width": ("INT", {"default": 512, "min": 0, "max": DIMMAX, "step": 8}),
|
||||
"custom_height": ("INT", {"default": 512, "min": 0, "max": DIMMAX, "step": 8}),
|
||||
"frame_load_cap": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}),
|
||||
"skip_first_frames": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "max": BIGMAX, "step": 1}),
|
||||
},
|
||||
"optional": {
|
||||
"batch_manager": ("VHS_BatchManager",)
|
||||
|
||||
+32
-7
@@ -4,8 +4,9 @@ import json
|
||||
import subprocess
|
||||
import numpy as np
|
||||
import re
|
||||
import datetime
|
||||
from typing import List
|
||||
from PIL import Image
|
||||
from PIL import Image, ExifTags
|
||||
from PIL.PngImagePlugin import PngInfo
|
||||
from pathlib import Path
|
||||
|
||||
@@ -15,7 +16,7 @@ from .image_latent_nodes import *
|
||||
from .load_video_nodes import LoadVideoUpload, LoadVideoPath
|
||||
from .load_images_nodes import LoadImagesFromDirectoryUpload, LoadImagesFromDirectoryPath
|
||||
from .batched_nodes import VAEEncodeBatched, VAEDecodeBatched
|
||||
from .utils import ffmpeg_path, get_audio, hash_path, validate_path, requeue_workflow
|
||||
from .utils import ffmpeg_path, get_audio, hash_path, validate_path, requeue_workflow, gifski_path
|
||||
|
||||
folder_paths.folder_names_and_paths["VHS_video_formats"] = (
|
||||
[
|
||||
@@ -45,6 +46,9 @@ def get_video_formats():
|
||||
video_format_path = folder_paths.get_full_path("VHS_video_formats", format_name + ".json")
|
||||
with open(video_format_path, 'r') as stream:
|
||||
video_format = json.load(stream)
|
||||
if "gifski_pass" in video_format and gifski_path is None:
|
||||
#Skip format
|
||||
continue
|
||||
widgets = [w[0] for w in gen_format_widgets(video_format)]
|
||||
if (len(widgets) > 0):
|
||||
formats.append(["video/" + format_name, widgets])
|
||||
@@ -106,7 +110,7 @@ def ffmpeg_process(args, video_format, video_metadata, file_path, env):
|
||||
with open(metadata_path, "w") as f:
|
||||
f.write(";FFMETADATA1\n")
|
||||
f.write(metadata)
|
||||
m_args = args[:1] + ["-i", metadata_path] + args[1:]
|
||||
m_args = args[:1] + ["-i", metadata_path] + args[1:] + ["-metadata", "creation_time=now"]
|
||||
with subprocess.Popen(m_args + [file_path], stderr=subprocess.PIPE,
|
||||
stdin=subprocess.PIPE, env=env) as proc:
|
||||
try:
|
||||
@@ -223,6 +227,7 @@ class VideoCombine:
|
||||
for x in extra_pnginfo:
|
||||
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
|
||||
video_metadata[x] = extra_pnginfo[x]
|
||||
metadata.add_text("CreationTime", datetime.datetime.now().isoformat(" ")[:19])
|
||||
|
||||
if batch_manager is not None and unique_id in batch_manager.outputs:
|
||||
(counter, output_process) = batch_manager.outputs[unique_id]
|
||||
@@ -263,6 +268,11 @@ class VideoCombine:
|
||||
image_kwargs = {}
|
||||
if format_ext == "gif":
|
||||
image_kwargs['disposal'] = 2
|
||||
if format_ext == "webp":
|
||||
#Save timestamp information
|
||||
exif = Image.Exif()
|
||||
exif[ExifTags.IFD.Exif] = {36867: datetime.datetime.now().isoformat(" ")[:19]}
|
||||
image_kwargs['exif'] = exif
|
||||
file = f"{filename}_{counter:05}.{format_ext}"
|
||||
file_path = os.path.join(full_output_folder, file)
|
||||
images = tensor_to_bytes(images)
|
||||
@@ -365,11 +375,26 @@ class VideoCombine:
|
||||
|
||||
output_files.append(file_path)
|
||||
|
||||
if "gifski_pass" in video_format:
|
||||
gif_output = f"{filename}_{counter:05}.gif"
|
||||
gif_output_path = os.path.join( full_output_folder, gif_output)
|
||||
gifski_args = [gifski_path] + video_format["gifski_pass"] \
|
||||
+ ["-o", gif_output_path, file_path]
|
||||
try:
|
||||
res = subprocess.run(gifski_args, env=env, check=True, capture_output=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise Exception("An error occured in the gifski subprocess:\n" \
|
||||
+ e.stderr.decode("utf-8"))
|
||||
if res.stderr:
|
||||
print(res.stderr.decode("utf-8"), end="", file=sys.stderr)
|
||||
#output format is actually an image and should be correctly marked
|
||||
#TODO: Evaluate a more consistent solution for this
|
||||
format = "image/gif"
|
||||
output_files.append(gif_output_path)
|
||||
file = gif_output
|
||||
|
||||
# Audio Injection after video is created, saves additional video with -audio.mp4
|
||||
|
||||
# Create audio file if input was provided
|
||||
if audio:
|
||||
elif audio is not None and audio() is not False:
|
||||
# Create audio file if input was provided
|
||||
output_file_with_audio = f"{filename}_{counter:05}-audio.{video_format['extension']}"
|
||||
output_file_with_audio_path = os.path.join(full_output_folder, output_file_with_audio)
|
||||
if "audio_pass" not in video_format:
|
||||
|
||||
+22
-4
@@ -8,6 +8,11 @@ import re
|
||||
import server
|
||||
from .logger import logger
|
||||
|
||||
BIGMIN = -(2**53-1)
|
||||
BIGMAX = (2**53-1)
|
||||
|
||||
DIMMAX = 8192
|
||||
|
||||
def ffmpeg_suitability(path):
|
||||
try:
|
||||
version = subprocess.run([path, "-version"], check=True,
|
||||
@@ -31,7 +36,7 @@ def ffmpeg_suitability(path):
|
||||
|
||||
|
||||
if "VHS_FORCE_FFMPEG_PATH" in os.environ:
|
||||
ffmpeg_path = os.env["VHS_FORCE_FFMPEG_PATH"]
|
||||
ffmpeg_path = os.environ.get("VHS_FORCE_FFMPEG_PATH")
|
||||
else:
|
||||
ffmpeg_paths = []
|
||||
try:
|
||||
@@ -51,9 +56,17 @@ else:
|
||||
if len(ffmpeg_paths) == 0:
|
||||
logger.error("No valid ffmpeg found.")
|
||||
ffmpeg_path = None
|
||||
elif len(ffmpeg_paths) == 1:
|
||||
#Evaluation of suitability isn't required, can take sole option
|
||||
#to reduce startup time
|
||||
ffmpeg_path = ffmpeg_paths[0]
|
||||
else:
|
||||
ffmpeg_path = max(ffmpeg_paths, key=ffmpeg_suitability)
|
||||
|
||||
gifski_path = os.environ.get("VHS_GIFSKI", None)
|
||||
if gifski_path is None:
|
||||
gifski_path = os.environ.get("JOV_GIFSKI", None)
|
||||
if gifski_path is None:
|
||||
gifski_path = shutil.which("gifski")
|
||||
|
||||
def get_sorted_dir_files_from_directory(directory: str, skip_first_images: int=0, select_every_nth: int=1, extensions: Iterable=None):
|
||||
directory = directory.strip()
|
||||
@@ -131,8 +144,13 @@ def get_audio(file, start_time=0, duration=0):
|
||||
args += ["-ss", str(start_time)]
|
||||
if duration > 0:
|
||||
args += ["-t", str(duration)]
|
||||
return subprocess.run(args + ["-f", "wav", "-"],
|
||||
stdout=subprocess.PIPE, check=True).stdout
|
||||
try:
|
||||
res = subprocess.run(args + ["-f", "wav", "-"],
|
||||
stdout=subprocess.PIPE, check=True).stdout
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.warning(f"Failed to extract audio from: {file}")
|
||||
return False
|
||||
return res
|
||||
|
||||
|
||||
def lazy_eval(func):
|
||||
|
||||
Reference in New Issue
Block a user