just convenience tools, not exposed but usable from CI etc... still need some work.
214 lines
6.9 KiB
Python
214 lines
6.9 KiB
Python
import argparse
|
|
import json
|
|
from PIL import Image, PngImagePlugin
|
|
from rich.console import Console
|
|
from rich import print
|
|
from rich_argparse import RichHelpFormatter
|
|
import os
|
|
from pathlib import Path
|
|
|
|
console = Console()
|
|
|
|
# BNK_CutoffSetRegions
|
|
# BNK_CutoffRegionsToConditioning
|
|
# BNK_CutoffBasePrompt
|
|
|
|
|
|
# Extracts metadata from a PNG image and returns it as a dictionary
|
|
def extract_metadata(image_path):
|
|
image = Image.open(image_path)
|
|
prompt = image.info.get("prompt", "")
|
|
workflow = image.info.get("workflow", "")
|
|
|
|
if workflow:
|
|
workflow = json.loads(workflow)
|
|
|
|
if prompt:
|
|
prompt = json.loads(prompt)
|
|
|
|
console.print(f"Metadata extracted from [cyan]{image_path}[/cyan].")
|
|
|
|
return {
|
|
"prompt": prompt,
|
|
"workflow": workflow,
|
|
}
|
|
|
|
|
|
# Embeds metadata into a PNG image
|
|
def embed_metadata(image_path, metadata):
|
|
image = Image.open(image_path)
|
|
o_metadata = image.info
|
|
|
|
pnginfo = PngImagePlugin.PngInfo()
|
|
if prompt := metadata.get("prompt"):
|
|
pnginfo.add_text("prompt", json.dumps(prompt))
|
|
elif "prompt" in o_metadata:
|
|
pnginfo.add_text("prompt", o_metadata["prompt"])
|
|
|
|
if workflow := metadata.get("workflow"):
|
|
pnginfo.add_text("workflow", json.dumps(workflow))
|
|
elif "workflow" in o_metadata:
|
|
pnginfo.add_text("workflow", o_metadata["workflow"])
|
|
|
|
imgp = Path(image_path)
|
|
output = imgp.with_stem(f"{imgp.stem}_comfy_embed")
|
|
index = 1
|
|
while output.exists():
|
|
output = imgp.with_stem(f"{imgp.stem}_{index}_comfy_embed").with_suffix(".png")
|
|
index += 1
|
|
|
|
image.save(output, pnginfo=pnginfo)
|
|
console.print(f"Metadata embedded into [cyan]{output}[/cyan].")
|
|
|
|
|
|
# CLI subcommand: extract
|
|
def extract(args):
|
|
input_files = []
|
|
for input_path in args.input:
|
|
if os.path.isdir(input_path):
|
|
folder_path = input_path
|
|
input_files.extend(
|
|
[
|
|
os.path.join(folder_path, file_name)
|
|
for file_name in os.listdir(folder_path)
|
|
if file_name.lower().endswith((".png", ".jpg", ".jpeg"))
|
|
]
|
|
)
|
|
else:
|
|
input_files.append(input_path)
|
|
|
|
if len(input_files) == 1:
|
|
metadata = extract_metadata(input_files[0])
|
|
if args.print_output:
|
|
print(json.dumps(metadata, indent=4))
|
|
else:
|
|
if not args.output:
|
|
output = Path(input_files[0]).with_suffix(".json")
|
|
index = 1
|
|
while output.exists():
|
|
output = (
|
|
Path(input_files[0])
|
|
.with_stem(f"{Path(input_files[0]).stem}_{index}")
|
|
.with_suffix(".json")
|
|
)
|
|
index += 1
|
|
else:
|
|
output = args.output
|
|
with open(output, "w") as file:
|
|
json.dump(metadata, file, indent=4)
|
|
console.print(f"Metadata extracted and saved to [cyan]{output}[/cyan].")
|
|
else:
|
|
metadata_dict = {}
|
|
for input_file in input_files:
|
|
metadata = extract_metadata(input_file)
|
|
filename = os.path.basename(input_file)
|
|
output = (
|
|
Path(args.output) / f"{filename}.json"
|
|
if args.output
|
|
else Path(input_file).with_suffix(".json")
|
|
)
|
|
index = 1
|
|
while output.exists():
|
|
output = Path(args.output).parent / f"{filename}_{index}.json"
|
|
index += 1
|
|
with open(output, "w") as file:
|
|
json.dump(metadata, file, indent=4)
|
|
metadata_dict[filename] = metadata
|
|
if args.output:
|
|
with open(args.output, "w") as file:
|
|
json.dump(metadata_dict, file, indent=4)
|
|
console.print(
|
|
f"Metadata extracted and saved to [cyan]{args.output}[/cyan]."
|
|
)
|
|
else:
|
|
console.print("Multiple metadata files created.")
|
|
|
|
|
|
# CLI subcommand: embed
|
|
def embed(args):
|
|
input_files = []
|
|
for input_path in args.input:
|
|
if os.path.isdir(input_path):
|
|
folder_path = input_path
|
|
input_files.extend(
|
|
[
|
|
os.path.join(folder_path, file_name)
|
|
for file_name in os.listdir(folder_path)
|
|
if file_name.lower().endswith(".json")
|
|
]
|
|
)
|
|
else:
|
|
input_files.append(input_path)
|
|
|
|
for input_file in input_files:
|
|
with open(input_file) as file:
|
|
metadata = json.load(file)
|
|
image_path = input_file.replace(".json", ".png")
|
|
if args.output:
|
|
output_dir = args.output
|
|
if os.path.isdir(output_dir):
|
|
output_path = os.path.join(output_dir, os.path.basename(image_path))
|
|
index = 1
|
|
while os.path.exists(output_path):
|
|
output_path = os.path.join(
|
|
output_dir,
|
|
f"{os.path.basename(image_path)}_{index}.png",
|
|
)
|
|
index += 1
|
|
else:
|
|
output_path = output_dir
|
|
else:
|
|
output_path = image_path.replace(".png", "_comfy_embed.png")
|
|
|
|
embed_metadata(image_path, metadata)
|
|
# os.rename(image_path, output_path)
|
|
console.print(f"Metadata embedded into [cyan]{output_path}[/cyan].")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Create the main CLI parser
|
|
parser = argparse.ArgumentParser(
|
|
prog="image-metadata-cli", formatter_class=RichHelpFormatter
|
|
)
|
|
subparsers = parser.add_subparsers(title="subcommands")
|
|
|
|
# Parser for the "extract" subcommand
|
|
extract_parser = subparsers.add_parser(
|
|
"extract",
|
|
help="Extract metadata from PNG image(s) or folder",
|
|
formatter_class=RichHelpFormatter,
|
|
)
|
|
extract_parser.add_argument(
|
|
"input", nargs="+", help="Input PNG image file(s) or folder path"
|
|
)
|
|
extract_parser.add_argument(
|
|
"--print",
|
|
dest="print_output",
|
|
action="store_true",
|
|
help="Print the output to stdout",
|
|
)
|
|
extract_parser.add_argument("--output", help="Output JSON file(s) or directory")
|
|
extract_parser.set_defaults(func=extract)
|
|
|
|
# Parser for the "embed" subcommand
|
|
embed_parser = subparsers.add_parser(
|
|
"embed",
|
|
help="Embed metadata into PNG image(s) or folder",
|
|
formatter_class=RichHelpFormatter,
|
|
)
|
|
embed_parser.add_argument(
|
|
"input", nargs="+", help="Input JSON file(s) or folder path"
|
|
)
|
|
embed_parser.add_argument("--output", help="Output PNG image file(s) or directory")
|
|
embed_parser.set_defaults(func=embed)
|
|
|
|
# Parse the command-line arguments and execute the appropriate subcommand
|
|
args = parser.parse_args()
|
|
if hasattr(args, "func"):
|
|
try:
|
|
args.func(args)
|
|
except ValueError as e:
|
|
console.print(f"[bold red]Error:[/bold red] {str(e)}")
|
|
else:
|
|
parser.print_help()
|