Skip to content

Commit 4ac9c25

Browse files
committed
🤏 Moderate Release - WAN Studio Redesign & 🌊📺 FlowState Video Preview Introduction
v1.0.2 Release * The new WAN Studio is completely streamlined and simplified. * Guess what? We now have a Video Preview node! Novel concept. * It's very minor but I removed some of the startup CMD/terminal output. See Releases for more info.
1 parent 02ef82f commit 4ac9c25

38 files changed

Lines changed: 995 additions & 118 deletions

FS_Constants.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
##
99
# OUTSIDE IMPORTS
1010
##
11-
import os, folder_paths, importlib
11+
import importlib
1212
import nodes
1313

1414

@@ -18,7 +18,7 @@
1818

1919
# SYSTEM INFO
2020
SYSTEM_NAME = 'FlowState Creator Suite'
21-
SYSTEM_VERSION = '0.1.5'
21+
SYSTEM_VERSION = '1.0.1'
2222

2323
# IMAGE PARAMETERS
2424
MAX_RESOLUTION=16384
@@ -32,14 +32,15 @@
3232

3333
KJNODES_INSTALLED = "PathchSageAttentionKJ" in nodes.NODE_CLASS_MAPPINGS
3434

35+
SAGE_AVAILABLE = False
3536
SageAttention = None
3637

37-
3838
try:
3939
importlib.import_module("sageattention")
4040
SAGE_ATTENTION_INSTALLED = True
4141
except:
4242
SAGE_ATTENTION_INSTALLED = False
4343

4444
if SAGE_ATTENTION_INSTALLED and KJNODES_INSTALLED:
45+
SAGE_AVAILABLE = True
4546
SageAttention = nodes.NODE_CLASS_MAPPINGS['PathchSageAttentionKJ']()

FS_Mappings.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,23 @@
2222
NODE_CLASS_MAPPINGS = {
2323
'FlowState_SimpleLatent': FlowState_SimpleLatent,
2424
'FlowState_VideoCreator': FlowState_VideoCreator,
25+
'FlowState_VideoPreview': FlowState_VideoPreview,
2526
'FlowState_LatentSource': FlowState_LatentSource,
2627
'FlowState_FluxEngine': FlowState_FluxEngine,
2728
'FlowState_WANStudio': FlowState_WANStudio,
29+
# 'FlowState_WANStudio_Pro': FlowState_WANStudio_Pro,
2830
'FlowState_Chef': FlowState_Chef,
2931
'FlowState_Chef_Ingredients': FlowState_Chef_Ingredients,
3032
}
3133

3234
NODE_DISPLAY_NAME_MAPPINGS = {
35+
'FlowState_VideoPreview': '🌊📺 FlowState Video Preview',
3336
'FlowState_VideoCreator': '🌊🎥 FlowState Video Creator',
3437
'FlowState_SimpleLatent': '🌊👌 FlowState Simple Latent',
3538
'FlowState_LatentSource': '🌊🌱 FlowState Latent Source',
3639
'FlowState_FluxEngine': '🌊🚒 FlowState Flux Engine',
37-
'FlowState_WANStudio': '🌊🎬 FlowState WAN Studio',
40+
'FlowState_WANStudio': '🌊🍿 FlowState WAN Studio',
41+
# 'FlowState_WANStudio_Pro': '🌊🎬 FlowState WAN Studio Pro',
3842
'FlowState_Chef': '🌊👩🏻‍🍳 FlowState Chef',
3943
'FlowState_Chef_Ingredients': '🌊🥗 FlowState Chef Ingredients',
4044
# 'FlowState_AssetForge': '🌊 FlowState Asset Forge',
@@ -44,7 +48,7 @@
4448
##
4549
# SYSTEM STATUS
4650
##
47-
for fs_node in NODE_CLASS_MAPPINGS:
48-
print(f'\t - 🟢 {fs_node}: {NODE_DISPLAY_NAME_MAPPINGS[fs_node]}')
51+
# for fs_node in NODE_CLASS_MAPPINGS:
52+
# print(f'\t - 🟢 {fs_node}: {NODE_DISPLAY_NAME_MAPPINGS[fs_node]}')
4953

5054
print(f'\t - ✅ Mappings Loaded.')

FS_Nodes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
from .FlowState_LatentSource import *
2020
from .FlowState_FluxEngine import *
2121
from .FlowState_VideoCreator import *
22+
from .FlowState_VideoPreview import *
2223
from .FlowState_WANStudio import *
24+
from .FlowState_WANStudio_Pro import *
2325
from .FlowState_Chef import *
2426
from .FlowState_Chef_Ingredients import *
2527

FS_Types.py

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ def __ne__(self, __value: object) -> bool:
8686
f' - Defines the height of the image.\n\n'
8787
)})
8888

89+
# VIDEO
90+
TYPE_VIDEO_IN = ('VIDEO', {'tooltip': (
91+
f' Video Input\n {"-" * TOOLTIP_UNDERLINE}\n'
92+
f' - The video input.\n\n'
93+
)})
94+
8995
# LATENT
9096
TYPE_LATENT_IN = ('LATENT', {'tooltip': (
9197
f' Latent Image\n {"-" * TOOLTIP_UNDERLINE}\n'
@@ -291,6 +297,11 @@ def pad_label(label):
291297
f' - This field is not functional. It is just a label for the group of settings below.\n\n'
292298
)})
293299

300+
TYPE_FLOWSTATE_LABEL_SAVING = ('STRING', {'default': pad_label('💾 File Save Settings'), 'tooltip': (
301+
f' Label\n {"-" * TOOLTIP_UNDERLINE}\n'
302+
f' - This field is not functional. It is just a label for the group of settings below.\n\n'
303+
)})
304+
294305
TYPE_FLOWSTATE_LABEL_SAMPLING = ('STRING', {'default': pad_label('🧪 Sampling Settings'), 'tooltip': (
295306
f' Label\n {"-" * TOOLTIP_UNDERLINE}\n'
296307
f' - This field is not functional. It is just a label for the group of settings below.\n\n'
@@ -316,6 +327,11 @@ def pad_label(label):
316327
# FLOWSTATE CREATOR GENERIC TYPES
317328
##
318329

330+
# BOOLEAN
331+
TYPE_SEED_SELECT = ('BOOLEAN', {'default': True, 'tooltip': (
332+
f' Fixed Output\n {"-" * TOOLTIP_UNDERLINE}\n'
333+
f' - Choose to get the same output every time or get variations.\n\n'
334+
)})
319335

320336
# SAGE ATTENTION
321337
enabled_sage_modes = [
@@ -344,44 +360,56 @@ def pad_label(label):
344360

345361

346362
# VIDEO
347-
TYPE_NUM_VIDEO_FRAMES = ('INT', {'default': 48, 'min': 1, 'max': nodes.MAX_RESOLUTION, 'step': 1, 'tooltip': (
363+
TYPE_NUM_VIDEO_FRAMES = ('INT', {'default': 25, 'min': 1, 'max': nodes.MAX_RESOLUTION, 'step': 1, 'tooltip': (
348364
f' Number of Video Frames\n {"-" * TOOLTIP_UNDERLINE}\n'
349365
f' - The number of frames you want in your final video.\n\n'
350366
)})
351-
352-
353-
##
354-
# FLOWSTATE VIDEO CREATOR
355-
##
356367
TYPE_FPS = ('INT', {'default': 12, 'min': 1, 'max': 120, 'tooltip': (
357368
f' Frames Per Second\n {"-" * TOOLTIP_UNDERLINE}\n'
358369
f' - The number of frames per second in the created video.\n\n'
359370
)})
360-
TYPE_FRAMES_IN = ('IMAGE', {'tooltip': (
361-
f' Video Frames\n {"-" * TOOLTIP_UNDERLINE}\n'
362-
f' - The frames used to create the video.\n\n'
363-
)})
364371
TYPE_AUDIO_IN = ('AUDIO', {'tooltip': (
365372
f' Video Audio\n {"-" * TOOLTIP_UNDERLINE}\n'
366373
f' - Optional audio to be added to the video.\n\n'
367374
)})
368-
TYPE_FILENAME_PREFIX = ('STRING', {'default': 'video/ComyUI', 'tooltip': (
369-
f' Filename Prefix\n {"-" * TOOLTIP_UNDERLINE}\n'
370-
f' - The prefix for the file to save.\n'
371-
f' - This may include formatting information such as %date:yyyy-MM-dd% or %Empty Latent Image.width% to include values from nodes.\n\n'
372-
)})
373375
TYPE_VIDEO_FORMAT = (['mp4', 'auto'], {
374376
'tooltip': (
375377
f' Video Format\n {"-" * TOOLTIP_UNDERLINE}\n'
376378
f' - The format to save the video as.\n\n'
377379
)
378380
})
381+
TYPE_SAVE_VIDEO_TYPE = (['mp4', 'gif'], {
382+
'tooltip': (
383+
f' Video Save Type\n {"-" * TOOLTIP_UNDERLINE}\n'
384+
f' - Save the video as an MP4 or a GIF.\n\n'
385+
)
386+
})
379387
TYPE_VIDEO_CODEC = (['h264', 'auto'], {
380388
'tooltip': (
381389
f' Video Codec\n {"-" * TOOLTIP_UNDERLINE}\n'
382390
f' - The codec to use for the video.\n\n'
383391
)
384392
})
393+
TYPE_FRAMES_IN = ('IMAGE', {'tooltip': (
394+
f' Video Frames\n {"-" * TOOLTIP_UNDERLINE}\n'
395+
f' - The frames used to create the video.\n\n'
396+
)})
397+
TYPE_BOOLEAN_SAVE_VIDEO = ('BOOLEAN', {'default': False, 'tooltip': (
398+
f' Save Video\n {"-" * TOOLTIP_UNDERLINE}\n'
399+
f' - Choose whether to save the video.\n\n'
400+
)})
401+
TYPE_FILENAME_PREFIX = ('STRING', {'default': 'video/WANStudio', 'tooltip': (
402+
f' Filename Prefix\n {"-" * TOOLTIP_UNDERLINE}\n'
403+
f' - The prefix for the file to save.\n'
404+
f' - This may include formatting information such as %date:yyyy-MM-dd% or %Empty Latent Image.width% to include values from nodes.\n\n'
405+
)})
406+
407+
408+
##
409+
# FLOWSTATE VIDEO CREATOR
410+
##
411+
412+
# NONE
385413

386414

387415
##
@@ -510,7 +538,13 @@ def pad_label(label):
510538
f' CLIP Vision Output\n {"-" * TOOLTIP_UNDERLINE}\n'
511539
f' - Optionally, use a CLIP Vision model.\n\n'
512540
)})
513-
TYPE_WAN_STUDIO_OUT = ('IMAGE', 'LATENT')
541+
TYPE_WAN_STUDIO_OUT = ('VIDEO', )
542+
TYPE_WAN_STUDIO_FILENAME_PREFIX = ('STRING', {'default': 'video/FlowState_WANStudio', 'tooltip': (
543+
f' Filename Prefix\n {"-" * TOOLTIP_UNDERLINE}\n'
544+
f' - The prefix for the file to save.\n'
545+
f' - This may include formatting information such as %date:yyyy-MM-dd% or %Empty Latent Image.width% to include values from nodes.\n'
546+
f' - This is ignored if you choose not to save the video.\n\n'
547+
)})
514548

515549

516550
##

FlowState_Chef.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
##
99
# SYSTEM STATUS
1010
##
11-
print(f'\t - 🟢 👩🏻‍🍳 Loaded Chef.')
11+
print(f'\t - 🟢 👩🏻‍🍳 Loaded FlowState Chef.')
1212

1313

1414
##

FlowState_Chef_Ingredients.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
##
99
# SYSTEM STATUS
1010
##
11-
print(f'\t - 🟢 🥗 Loaded Chef Ingredients.')
11+
print(f'\t - 🟢 🥗 Loaded FlowState Chef Ingredients.')
1212

1313

1414
##

FlowState_FluxEngine.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
##
99
# SYSTEM STATUS
1010
##
11-
print(f'\t - 🟢 🚒 Loaded Flux Engine.')
11+
print(f'\t - 🟢 🚒 Loaded FlowState Flux Engine.')
1212

1313

1414
##

FlowState_LatentSource.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
##
1010
# SYSTEM STATUS
1111
##
12-
print(f'\t - 🟢 🌱 Loaded Latent Source.')
12+
print(f'\t - 🟢 🌱 Loaded FlowState Latent Source.')
1313

1414

1515
##

FlowState_Node.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
##
1111
import os, sys, time
1212

13+
from datetime import datetime
14+
1315

1416
##
1517
# FS IMPORTS
@@ -63,7 +65,7 @@ def print_status(self, messages, init=False, end=False, error=False):
6365
if init:
6466
print(f'\n\n\n --- STARTING {self.node_name} ---')
6567

66-
print(f'\n\n')
68+
print(f'\n')
6769

6870
if error: print('-' * 100)
6971

@@ -90,3 +92,36 @@ def get_mins_and_secs(self, start_time):
9092
secs = int(duration - mins * 60)
9193
return round(duration, 4), mins, secs
9294

95+
def check_tensor_equality(self, t1, t2, tensor_type='image'):
96+
self.print_status([(f'Checking Tensor ({tensor_type}) Equality...',)])
97+
98+
has_t1 = t1 != None
99+
has_t2 = t2 != None
100+
101+
has_neither = not has_t1 and not has_t2
102+
has_t2_but_not_t1 = has_t2 and not has_t1
103+
has_t1_but_not_t2 = has_t1 and not has_t2
104+
one_missing = has_t2_but_not_t1 or has_t1_but_not_t2
105+
106+
if one_missing:
107+
return False
108+
109+
if has_neither:
110+
return True
111+
112+
same_shape = t1.shape == t2.shape
113+
114+
if not same_shape:
115+
return False
116+
117+
if same_shape:
118+
equal_tensors = t1.equal(t2)
119+
120+
if not equal_tensors:
121+
return False
122+
123+
return True
124+
125+
def get_formatted_time(self):
126+
return datetime.now().strftime('%Y-%m-%d %H-%M-%S.%f')[:-3]
127+

FlowState_SaveTempVideo.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Project: FlowState Video Preview
2+
# Description: Simple preview video don't know why this isn't a node.
3+
# Version: 0.0.1
4+
# Author: Johnathan Chivington
5+
# Contact: flowstateeng@gmail.com | youtube.com/@flowstateeng
6+
7+
8+
##
9+
# OUTSIDE IMPORTS
10+
##
11+
import os
12+
import folder_paths
13+
14+
from datetime import datetime
15+
from comfy_api.input import VideoInput
16+
from comfy_api.util import VideoContainer
17+
from comfy_api.latest import io, ui
18+
from comfy.cli_args import args
19+
20+
21+
class FlowState_SaveTempVideo(io.ComfyNode):
22+
file_prefix = f'FlowState_VideoPreview'
23+
format = 'mp4'
24+
codec = 'h264'
25+
26+
temp_dir = folder_paths.get_temp_directory()
27+
28+
@classmethod
29+
def define_schema(cls):
30+
return io.Schema(
31+
node_id='FlowState_SaveTempVideo',
32+
display_name='FlowState Save Temp Video',
33+
category='FlowState Creator Suite/Video',
34+
description='Saves the video previews to your ComfyUI temp directory.',
35+
inputs=[
36+
io.Video.Input('video'),
37+
io.String.Input('filename_prefix'),
38+
io.Combo.Input('format'),
39+
io.Combo.Input('codec')
40+
],
41+
outputs=[],
42+
hidden=[io.Hidden.prompt, io.Hidden.extra_pnginfo],
43+
is_output_node=True,
44+
)
45+
46+
@classmethod
47+
def save_metadata(cls):
48+
null_metadata = None
49+
50+
if args.disable_metadata:
51+
return null_metadata
52+
53+
metadata = {}
54+
55+
if cls.hidden.extra_pnginfo != None:
56+
metadata.update(cls.hidden.extra_pnginfo)
57+
58+
if cls.hidden.prompt != None:
59+
metadata['prompt'] = cls.hidden.prompt
60+
61+
have_metadata = len(metadata) > 0
62+
63+
return metadata if have_metadata else null_metadata
64+
65+
@classmethod
66+
def save_video(cls, video, tmp_dir_path, filename, metadata):
67+
full_path = os.path.join(tmp_dir_path, filename)
68+
69+
video.save_to(
70+
full_path, format=cls.format, codec=cls.codec, metadata=metadata
71+
)
72+
73+
@classmethod
74+
def get_path_parts(cls, width, height):
75+
tmp_dir_path, filename, counter, subfolder, prefix = folder_paths.get_save_image_path(
76+
cls.file_prefix, cls.temp_dir, width, height
77+
)
78+
79+
return tmp_dir_path, filename, subfolder
80+
81+
@classmethod
82+
def execute(cls, video: VideoInput) -> io.NodeOutput:
83+
width, height = video.get_dimensions()
84+
85+
tmp_dir_path, filename, subfolder = cls.get_path_parts(width, height)
86+
87+
saved_metadata = cls.save_metadata()
88+
video_extension = VideoContainer.get_extension(cls.format)
89+
90+
timestamp = datetime.now().strftime('%Y-%m-%d %H-%M-%S.%f')[:-3]
91+
tmp_filename = f'{filename} - {timestamp}.{video_extension}'
92+
93+
cls.save_video(video, tmp_dir_path, tmp_filename, saved_metadata)
94+
95+
saved_result = ui.SavedResult(tmp_filename, subfolder, io.FolderType.temp)
96+
video_preview = ui.PreviewVideo([saved_result])
97+
98+
return io.NodeOutput(ui=video_preview)
99+

0 commit comments

Comments
 (0)