diff --git a/README.md b/README.md
index ed1119a..8461d60 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,10 @@ If you prefer trying a workflow to reading docs, use of the nodes is illustrated
+## New in 1.4 ##
+
+Video previews!
+
## New in 1.3.2 ##
- works with old mask editor as well
@@ -119,6 +123,14 @@ You could also use this to make a larger batch of images (repeat a value and the
Note that this uses zero indexing (the first image is '0').
+### video_frames
+
+If you set this to greater than one, the image filter node will batch up each set of n images and treat them as a video snip.
+
+Set the FPS for playback in the global settings.
+
+This is a new, experimental feature, so please report any issues...
+
---
## Mask Image Filter
diff --git a/__init__.py b/__init__.py
index 1e32ada..2ab64d5 100644
--- a/__init__.py
+++ b/__init__.py
@@ -9,7 +9,7 @@ from .image_filter import ImageFilter, MaskImageFilter, TextImageFilterWithExtra
from .list_utility_nodes import PickFromList, BatchFromImageList, ImageListFromBatch
from .string_utility_nodes import SplitByCommas, StringToFloat, StringToInt
-VERSION = "1.3.2"
+VERSION = "1.4"
WEB_DIRECTORY = "./js"
NODE_CLASS_MAPPINGS= {
diff --git a/image_filter.py b/image_filter.py
index 2ff5e3b..eca727e 100644
--- a/image_filter.py
+++ b/image_filter.py
@@ -90,6 +90,7 @@ class ImageFilter(PreviewImage):
FUNCTION = "func"
CATEGORY = "image_filter"
OUTPUT_NODE = False
+ DESCRIPTION = "Allows you to preview images and choose which, if any to proceed with"
@classmethod
def INPUT_TYPES(s):
@@ -107,6 +108,7 @@ class ImageFilter(PreviewImage):
"extra2" : ("STRING", {"default":""}),
"extra3" : ("STRING", {"default":""}),
"pick_list" : ("STRING", {"default":"", "tooltip":"If a comma separated list of integers is provided, the images with these indices will be selected automatically."}),
+ "video_frames" : ("INT", {"default":1, "min":1, "tooltip": "treat each block of n images as a video"}),
},
"hidden": HIDDEN,
}
@@ -115,7 +117,7 @@ class ImageFilter(PreviewImage):
def IS_CHANGED(cls, pick_list, **kwargs):
return pick_list or float("NaN")
- def func(self, images, timeout, ontimeout, uid, node_identifier, tip="", extra1="", extra2="", extra3="", latents=None, masks=None, pick_list:str="", **kwargs):
+ def func(self, images, timeout, ontimeout, uid, node_identifier, tip="", extra1="", extra2="", extra3="", latents=None, masks=None, pick_list:str="", video_frames:int=1, **kwargs):
e1, e2, e3 = extra1, extra2, extra3
B = images.shape[0]
@@ -127,7 +129,7 @@ class ImageFilter(PreviewImage):
if len(images_to_return) == 0:
all_the_same = ( B and all( (images[i]==images[0]).all() for i in range(1,B) ))
urls:list[str] = self.save_images(images=images, **kwargs)['ui']['images']
- payload = {"uid": uid, "urls":urls, "allsame":all_the_same, "extras":[extra1, extra2, extra3], "tip":tip}
+ payload = {"uid": uid, "urls":urls, "allsame":all_the_same, "extras":[extra1, extra2, extra3], "tip":tip, "video_frames":video_frames}
response = send_with_resend(payload, timeout, uid, node_identifier)
@@ -138,10 +140,13 @@ class ImageFilter(PreviewImage):
if ontimeout=='send none': images_to_return = []
if ontimeout=='send all': images_to_return = [*range(len(images))]
if ontimeout=='send first': images_to_return = [0,]
- if ontimeout=='send last': images_to_return = [len(images)-1,]
+ if ontimeout=='send last': images_to_return = [len(images)//video_frames,]
if len(images_to_return) == 0: raise InterruptProcessingException()
+ if video_frames>1:
+ images_to_return = [ key*video_frames + frm for key in images_to_return for frm in range(video_frames) ]
+
images = torch.stack(list(images[i] for i in images_to_return))
latents = {"samples": torch.stack(list(latents['samples'][i] for i in images_to_return))} if latents is not None else None
masks = torch.stack(list(masks[i] for i in images_to_return)) if masks is not None else None
diff --git a/js/image_filter.js b/js/image_filter.js
index 40f9a16..3fc5d77 100644
--- a/js/image_filter.js
+++ b/js/image_filter.js
@@ -40,6 +40,12 @@ app.registerExtension({
type: "boolean",
defaultValue: false
},
+ {
+ id: "ImageFilter.FPS",
+ name: "Video Frames per Second",
+ type: "int",
+ defaultValue: 1,
+ }
],
setup() {
create('link', null, document.getElementsByTagName('HEAD')[0],
diff --git a/js/popup.js b/js/popup.js
index d76665b..6524c65 100644
--- a/js/popup.js
+++ b/js/popup.js
@@ -247,6 +247,8 @@ class Popup extends HTMLSpanElement {
handle_urls(detail) {
this.n_extras = detail.extras ? detail.extras.length : 0
+ this.video_frames = detail.video_frames || 1
+
this.extras.innerHTML = ''
for (let i=0; i{
console.log(url)
- const img = create('img', null, this.grid, {src:get_full_url(url)})
+ if (i%this.video_frames == 0) {
+ latestImage = create('img', null, this.grid, {src:get_full_url(url)})
+ latestImage.onload = this.layout.bind(this)
+ latestImage.image_index = i/this.video_frames
+ latestImage.addEventListener('mouseover', (e)=>this.on_mouse_enter(latestImage))
+ latestImage.addEventListener('mouseout', (e)=>this.on_mouse_out(latestImage))
+ latestImage.frames = [get_full_url(url),]
+ } else {
+ latestImage.frames.push(get_full_url(url))
+ }
if (detail.mask_urls) { create('img', null, this.overlaygrid, {src:get_full_url(detail.mask_urls[i])})}
- img.onload = this.layout.bind(this)
- img.image_index = i
- img.addEventListener('mouseover', (e)=>this.on_mouse_enter(img))
- img.addEventListener('mouseout', (e)=>this.on_mouse_out(img))
+
})
this.layout()
+
+ if (this.video_frames>1) {
+ this.frame = 0
+ setTimeout(this.advance_videos.bind(this), 1000)
+ }
}
+ advance_videos() {
+ if (this.state == State.INACTIVE) return
+
+ this.frame = (this.frame+1)%this.video_frames
+ Array.from(this.grid.children).forEach((img)=>{img.src = img.frames[this.frame]})
+
+ const fps = app.ui.settings.getSettingValue("ImageFilter.FPS")
+ const delay = (fps>0) ? 1000/fps : 1000
+ setTimeout(this.advance_videos.bind(this), delay)
+ }
+
on_mouse_enter(img) {
this.mouse_is_over = img
this.redraw()
diff --git a/pyproject.toml b/pyproject.toml
index c330c33..49496a1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
[project]
name = "cg-image-filter"
description = "A set of custom nodes that pause a workflow while you select images, add masks, or edit text."
-version = "1.3.2"
+version = "1.4"
license = { file = "LICENSE" }
[project.urls]