1.4 - video filter

This commit is contained in:
Chris
2025-04-28 17:49:51 +10:00
parent 1e079fd422
commit a1ae2ae24b
6 changed files with 61 additions and 11 deletions
+12
View File
@@ -19,6 +19,10 @@ If you prefer trying a workflow to reading docs, use of the nodes is illustrated
<img src="https://github.com/chrisgoringe/cg-image-filter/raw/main/images/seahorse.png" alt="Seahorse" width="200" height="200">
## 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
+1 -1
View File
@@ -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= {
+8 -3
View File
@@ -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
+6
View File
@@ -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],
+33 -6
View File
@@ -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<this.n_extras; i++) { create('input', 'extra', this.extras, {value:detail.extras[i]}) }
@@ -265,7 +267,8 @@ class Popup extends HTMLSpanElement {
this.state = State.FILTER
}
this.n_images = detail.urls?.length
this.n_images = detail.urls?.length
this.laidOut = false
this.title_bar.innerText = app.graph._nodes_by_id[detail.uid]?.title ?? "Image Filter"
@@ -274,20 +277,44 @@ class Popup extends HTMLSpanElement {
this.grid.innerHTML = ''
this.overlaygrid.innerHTML = ''
var latestImage = null
detail.urls.forEach((url, 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()
+1 -1
View File
@@ -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]