Files
myshell-ai-ComfyUI-ShellAge…/comfy-nodes/output_image.py
T
Chengwei Ouyang 73e610e31a feat: add encryption support for saved images and videos
- Implemented XOR encryption for image files in output_image.py, allowing users to save encrypted images.
- Introduced a new module output_video_encrypt.py for combining images into videos with optional encryption.
- Added encryption options in the save_images method and video combine functionality, ensuring output files cannot be viewed directly without decryption.
2025-12-15 17:27:23 +08:00

140 lines
4.8 KiB
Python

import folder_paths
from nodes import SaveImage
import os
import numpy as np
# Fixed encryption key - use the same key for decryption
ENCRYPTION_KEY = b"ShellAgentSecretKey2024!"
def xor_encrypt_file(filepath, key):
"""
Fast XOR encryption using NumPy vectorization.
Completely corrupts the file so it cannot be viewed directly.
To decrypt, simply call this function again with the same key.
"""
with open(filepath, 'rb') as f:
data = np.frombuffer(f.read(), dtype=np.uint8)
# Create repeated key array matching data length
key_array = np.frombuffer(key * ((len(data) // len(key)) + 1), dtype=np.uint8)[:len(data)]
# Vectorized XOR - 50-100x faster than Python loop
encrypted = np.bitwise_xor(data, key_array)
with open(filepath, 'wb') as f:
f.write(encrypted.tobytes())
def xor_decrypt_file(filepath, key):
"""
Decrypt a file encrypted with xor_encrypt_file.
XOR encryption is symmetric, so decryption is the same as encryption.
"""
xor_encrypt_file(filepath, key)
class ShellAgentSaveImages(SaveImage):
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"images": ("IMAGE", {"tooltip": "The images to save."}),
"output_name": ("STRING", {"multiline": False, "default": "output_image"},),
"filename_prefix": ("STRING", {"default": "ComfyUI", "tooltip": "The prefix for the file to save. This may include formatting information such as %date:yyyy-MM-dd% or %Empty Latent Image.width% to include values from nodes."}),
"encrypt": ("BOOLEAN", {"default": False, "tooltip": "If enabled, the saved image will be encrypted and cannot be viewed directly. Use the same key to decrypt elsewhere."})
},
"hidden": {
"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"
},
}
CATEGORY = "shellagent"
@classmethod
def validate(cls, **kwargs):
schema = {
"title": kwargs["output_name"],
"type": "array",
"items": {
"type": "string",
"url_type": "image",
}
}
return schema
def save_images(self, images, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None, encrypt=False, **extra_kwargs):
results = super().save_images(images, filename_prefix, prompt, extra_pnginfo)
# Encrypt saved files if encrypt option is enabled
if encrypt:
for img_info in results.get("ui", {}).get("images", []):
filename = img_info["filename"]
subfolder = img_info.get("subfolder", "")
full_path = os.path.join(self.output_dir, subfolder, filename)
if os.path.exists(full_path):
xor_encrypt_file(full_path, ENCRYPTION_KEY)
results["shellagent_kwargs"] = extra_kwargs
return results
class ShellAgentSaveImage(ShellAgentSaveImages):
@classmethod
def validate(cls, **kwargs):
schema = {
"title": kwargs["output_name"],
"type": "string",
"url_type": "image",
}
return schema
class ShellAgentSaveVideoVHS:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"filenames": ("VHS_FILENAMES", {"tooltip": "The filenames to save."}),
"output_name": ("STRING", {"multiline": False, "default": "output_video"},),
},
}
RETURN_TYPES = ()
FUNCTION = "save_video"
OUTPUT_NODE = True
CATEGORY = "shellagent"
DESCRIPTION = "Saves the input images to your ComfyUI output directory."
@classmethod
def validate(cls, **kwargs):
schema = {
"title": kwargs["output_name"],
"type": "string",
"url_type": "video",
}
return schema
def save_video(self, filenames, **kwargs):
status, output_files = filenames
if len(output_files) == 0:
raise ValueError("the filenames are empty")
print("output_files", output_files)
video_path = output_files[-1]
cwd = os.getcwd()
# preview_image = os.path.relpath(preview_image)
video_path = os.path.relpath(video_path, folder_paths.base_path)
results = {"ui": {"video": [video_path]}}
return results
NODE_CLASS_MAPPINGS = {
"ShellAgentPluginSaveImage": ShellAgentSaveImage,
"ShellAgentPluginSaveImages": ShellAgentSaveImages,
"ShellAgentPluginSaveVideoVHS": ShellAgentSaveVideoVHS,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ShellAgentPluginSaveImage": "Save Image (ShellAgent Plugin)",
"ShellAgentPluginSaveImages": "Save Images (ShellAgent Plugin)",
"ShellAgentPluginSaveVideoVHS": "Save Video - VHS (ShellAgent Plugin)",
}