|
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() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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, |
|
} |
|
|
|
|
|
|
|
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].") |
|
|
|
|
|
|
|
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.") |
|
|
|
|
|
|
|
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) |
|
|
|
console.print(f"Metadata embedded into [cyan]{output_path}[/cyan].") |
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
parser = argparse.ArgumentParser( |
|
prog="image-metadata-cli", formatter_class=RichHelpFormatter |
|
) |
|
subparsers = parser.add_subparsers(title="subcommands") |
|
|
|
|
|
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) |
|
|
|
|
|
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) |
|
|
|
|
|
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() |
|
|