1.4 - video filter
This commit is contained in:
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user