mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-07-06 22:51:18 +08:00
Merge eda4ba84f2 into b08debceca
This commit is contained in:
commit
7e34b70ae6
@ -56,6 +56,9 @@ PREVIEWABLE_MEDIA_TYPES = frozenset({'images', 'video', 'audio', '3d', 'text'})
|
|||||||
# 3D file extensions for preview fallback (no dedicated media_type exists)
|
# 3D file extensions for preview fallback (no dedicated media_type exists)
|
||||||
THREE_D_EXTENSIONS = frozenset({'.obj', '.fbx', '.gltf', '.glb', '.usdz'})
|
THREE_D_EXTENSIONS = frozenset({'.obj', '.fbx', '.gltf', '.glb', '.usdz'})
|
||||||
|
|
||||||
|
# Text file extensions for preview fallback (the formats SaveText can produce)
|
||||||
|
TEXT_EXTENSIONS = frozenset({'.txt', '.md', '.json'})
|
||||||
|
|
||||||
|
|
||||||
def has_3d_extension(filename: str) -> bool:
|
def has_3d_extension(filename: str) -> bool:
|
||||||
lower = filename.lower()
|
lower = filename.lower()
|
||||||
@ -143,9 +146,10 @@ def is_previewable(media_type: str, item: dict) -> bool:
|
|||||||
Maintains backwards compatibility with existing logic.
|
Maintains backwards compatibility with existing logic.
|
||||||
|
|
||||||
Priority:
|
Priority:
|
||||||
1. media_type is 'images', 'video', 'audio', or '3d'
|
1. media_type is 'images', 'video', 'audio', '3d', or 'text'
|
||||||
2. format field starts with 'video/' or 'audio/'
|
2. format field starts with 'video/' or 'audio/'
|
||||||
3. filename has a 3D extension (.obj, .fbx, .gltf, .glb, .usdz)
|
3. filename has a 3D extension (.obj, .fbx, .gltf, .glb, .usdz)
|
||||||
|
4. filename has a text extension (.txt, .md, .json, ...)
|
||||||
"""
|
"""
|
||||||
if media_type in PREVIEWABLE_MEDIA_TYPES:
|
if media_type in PREVIEWABLE_MEDIA_TYPES:
|
||||||
return True
|
return True
|
||||||
@ -156,10 +160,12 @@ def is_previewable(media_type: str, item: dict) -> bool:
|
|||||||
if fmt and (fmt.startswith('video/') or fmt.startswith('audio/')):
|
if fmt and (fmt.startswith('video/') or fmt.startswith('audio/')):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Check for 3D files by extension
|
# Check for 3D and text files by extension
|
||||||
filename = item.get('filename', '').lower()
|
filename = item.get('filename', '').lower()
|
||||||
if any(filename.endswith(ext) for ext in THREE_D_EXTENSIONS):
|
if any(filename.endswith(ext) for ext in THREE_D_EXTENSIONS):
|
||||||
return True
|
return True
|
||||||
|
if any(filename.endswith(ext) for ext in TEXT_EXTENSIONS):
|
||||||
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -255,6 +261,10 @@ def get_outputs_summary(outputs: dict) -> tuple[int, Optional[dict]]:
|
|||||||
Preview priority (matching frontend):
|
Preview priority (matching frontend):
|
||||||
1. type="output" with previewable media
|
1. type="output" with previewable media
|
||||||
2. Any previewable media
|
2. Any previewable media
|
||||||
|
|
||||||
|
Text content entries (strings under 'text') are preview-only metadata,
|
||||||
|
matching the frontend's METADATA_KEYS: they can serve as the fallback
|
||||||
|
preview but are not counted as outputs.
|
||||||
"""
|
"""
|
||||||
count = 0
|
count = 0
|
||||||
preview_output = None
|
preview_output = None
|
||||||
@ -275,7 +285,6 @@ def get_outputs_summary(outputs: dict) -> tuple[int, Optional[dict]]:
|
|||||||
if normalized is None:
|
if normalized is None:
|
||||||
# Not a 3D file string — check for text preview
|
# Not a 3D file string — check for text preview
|
||||||
if media_type == 'text':
|
if media_type == 'text':
|
||||||
count += 1
|
|
||||||
if preview_output is None:
|
if preview_output is None:
|
||||||
if isinstance(item, tuple):
|
if isinstance(item, tuple):
|
||||||
text_value = item[0] if item else ''
|
text_value = item[0] if item else ''
|
||||||
|
|||||||
73
comfy_extras/nodes_text.py
Normal file
73
comfy_extras/nodes_text.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
from typing_extensions import override
|
||||||
|
from comfy_api.latest import io, ComfyExtension, ui
|
||||||
|
import folder_paths
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
class SaveTextNode(io.ComfyNode):
|
||||||
|
"""Save text content to .txt, .md, or .json."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def define_schema(cls):
|
||||||
|
return io.Schema(
|
||||||
|
node_id="SaveText",
|
||||||
|
search_aliases=["save text", "write text", "export text"],
|
||||||
|
display_name="Save Text",
|
||||||
|
category="text",
|
||||||
|
description="Save text content to a file in the output directory.",
|
||||||
|
inputs=[
|
||||||
|
io.String.Input("text", force_input=True),
|
||||||
|
io.String.Input("filename_prefix", default="ComfyUI"),
|
||||||
|
io.Combo.Input("format", options=["txt", "md", "json"], default="txt"),
|
||||||
|
],
|
||||||
|
outputs=[io.String.Output(display_name="text")],
|
||||||
|
is_output_node=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def execute(cls, text, filename_prefix, format):
|
||||||
|
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(
|
||||||
|
filename_prefix,
|
||||||
|
folder_paths.get_output_directory(),
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
|
||||||
|
file = f"{filename}_{counter:05}.{format}"
|
||||||
|
filepath = os.path.join(full_output_folder, file)
|
||||||
|
|
||||||
|
if format == "json":
|
||||||
|
# tries to pretty print otherwise saves normally
|
||||||
|
try:
|
||||||
|
data = json.loads(text)
|
||||||
|
with open(filepath, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
logging.warning("Saved JSON as a raw text")
|
||||||
|
with open(filepath, "w", encoding="utf-8") as f:
|
||||||
|
f.write(text)
|
||||||
|
else:
|
||||||
|
with open(filepath, "w", encoding="utf-8") as f:
|
||||||
|
f.write(text)
|
||||||
|
|
||||||
|
return io.NodeOutput(
|
||||||
|
text,
|
||||||
|
ui={
|
||||||
|
"text": (text,),
|
||||||
|
"files": [
|
||||||
|
ui.SavedResult(file, subfolder, io.FolderType.output)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
class TextExtension(ComfyExtension):
|
||||||
|
@override
|
||||||
|
async def get_node_list(self) -> list[type[io.ComfyNode]]:
|
||||||
|
return [
|
||||||
|
SaveTextNode
|
||||||
|
]
|
||||||
|
|
||||||
|
async def comfy_entrypoint() -> TextExtension:
|
||||||
|
return TextExtension()
|
||||||
Loading…
Reference in New Issue
Block a user