feat: 🚧 add my CLIs

just convenience tools, not exposed but usable from CI etc...
still need some work.
This commit is contained in:
melMass
2023-07-22 00:12:13 +02:00
parent 27500ca432
commit 44eaae5c79
3 changed files with 467 additions and 0 deletions
+142
View File
@@ -0,0 +1,142 @@
import glob
from pathlib import Path
import uuid
import sys
from typing import List
sys.path.append((Path(__file__).parent / "extern").as_posix())
import argparse
from rich_argparse import RichHelpFormatter
from rich.console import Console
from rich.progress import Progress
import numpy as np
import subprocess
def write_prores_444_video(output_file, frames: List[np.ndarray], fps):
# Convert float images to the range of 0-65535 (12-bit color depth)
frames = [(frame * 65535).clip(0, 65535).astype(np.uint16) for frame in frames]
height, width, _ = frames[0].shape
# Prepare the FFmpeg command
command = [
"ffmpeg",
"-y", # Overwrite output file if it already exists
"-f",
"rawvideo",
"-vcodec",
"rawvideo",
"-s",
f"{width}x{height}",
"-pix_fmt",
"rgb48le",
"-r",
str(fps),
"-i",
"-",
"-c:v",
"prores_ks",
"-profile:v",
"4",
"-pix_fmt",
"yuva444p10le",
"-r",
str(fps),
"-y", # Overwrite output file if it already exists
output_file,
]
process = subprocess.Popen(command, stdin=subprocess.PIPE)
for frame in frames:
process.stdin.write(frame.tobytes())
process.stdin.close()
process.wait()
if __name__ == "__main__":
default_output = f"./output_{uuid.uuid4()}.mov"
parser = argparse.ArgumentParser(
description="FILM frame interpolation", formatter_class=RichHelpFormatter
)
parser.add_argument("inputs", nargs="*", help="Input image files")
parser.add_argument("--output", help="Output JSON file", default=default_output)
parser.add_argument("-v", "--verbose", action="store_true", help="Verbose mode")
parser.add_argument(
"--glob", help="Enable glob pattern matching", metavar="PATTERN"
)
parser.add_argument(
"--interpolate", type=int, default=4, help="Time for interpolated frames"
)
parser.add_argument("--fps", type=int, default=30, help="Out FPS")
align = 64
block_width = 2
block_height = 2
args = parser.parse_args()
# - checks
if not args.glob and not args.inputs:
parser.error("Either --glob flag or inputs must be provided.")
if args.glob:
glob_pattern = args.glob
try:
pattern_path = str(Path(glob_pattern).expanduser().resolve())
if not any(glob.glob(pattern_path)):
raise ValueError(f"No files found for glob pattern: {glob_pattern}")
except Exception as e:
console = Console()
console.print(
f"[bold red]Error: Invalid glob pattern '{glob_pattern}': {e}[/bold red]"
)
exit(1)
else:
glob_pattern = None
input_files: List[Path] = []
if glob_pattern:
input_files = [
Path(p)
for p in list(glob.glob(str(Path(glob_pattern).expanduser().resolve())))
]
else:
input_files = [Path(p) for p in args.inputs]
console = Console()
console.print("Input Files:", style="bold", end=" ")
console.print(f"{len(input_files):03d} files", style="cyan")
# for input_file in args.inputs:
# console.print(f"- {input_file}", style="cyan")
console.print("\nOutput File:", style="bold", end=" ")
console.print(f"{Path(args.output).resolve().absolute()}", style="cyan")
with Progress(console=console, auto_refresh=True) as progress:
from frame_interpolation.eval import util
from frame_interpolation.eval import util, interpolator
# files = Path(pth).rglob("*.png")
model = interpolator.Interpolator(
"G:/MODELS/FILM/pretrained_models/film_net/Style", None
) # [2,2]
task = progress.add_task("[cyan]Interpolating frames...", total=1)
frames = list(
util.interpolate_recursively_from_files(
[x.as_posix() for x in input_files], args.interpolate, model
)
)
# mediapy.write_video(args.output, frames, fps=args.fps)
write_prores_444_video(args.output, frames, fps=args.fps)
progress.update(task, advance=1)
progress.refresh()
+112
View File
@@ -0,0 +1,112 @@
from pathlib import Path
from PIL import Image
from PIL.PngImagePlugin import PngImageFile, PngInfo
import json
from pprint import pprint
import argparse
from rich.console import Console
from rich.progress import Progress
from rich_argparse import RichHelpFormatter
def parse_a111(params, verbose=False):
# params = [p.split(": ") for p in params.split("\n")]
params = params.split("\n")
prompt = params[0].strip()
neg = params[1].split(":")[1].strip()
settings = {}
try:
settings = {
s.split(":")[0].strip(): s.split(":")[1].strip()
for s in params[2].split(",")
}
except IndexError:
settings = {"raw": params[2].strip()}
if verbose:
print(f"PROMPT: {prompt}")
print(f"NEG: {neg}")
print("SETTINGS:")
pprint(settings, indent=4)
return {"prompt": prompt, "negative": neg, "settings": settings}
import glob
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Crude metadata extractor from A111 pngs",
formatter_class=RichHelpFormatter
)
parser.add_argument("inputs", nargs="*", help="Input image files")
parser.add_argument("--output", help="Output JSON file")
parser.add_argument("-v", "--verbose", action="store_true", help="Verbose mode")
parser.add_argument(
"--glob", help="Enable glob pattern matching", metavar="PATTERN"
)
args = parser.parse_args()
# - checks
if not args.glob and not args.inputs:
parser.error("Either --glob flag or inputs must be provided.")
if args.glob:
glob_pattern = args.glob
try:
pattern_path = str(Path(glob_pattern).expanduser().resolve())
if not any(glob.glob(pattern_path)):
raise ValueError(f"No files found for glob pattern: {glob_pattern}")
except Exception as e:
console = Console()
console.print(
f"[bold red]Error: Invalid glob pattern '{glob_pattern}': {e}[/bold red]"
)
exit(1)
else:
glob_pattern = None
input_files = []
if glob_pattern:
input_files = list(glob.glob(str(Path(glob_pattern).expanduser().resolve())))
else:
input_files = [Path(p) for p in args.inputs]
console = Console()
console.print("Input Files:", style="bold", end=" ")
console.print(f"{len(input_files):03d} files", style="cyan")
# for input_file in args.inputs:
# console.print(f"- {input_file}", style="cyan")
console.print("\nOutput File:", style="bold", end=" ")
console.print(f"{Path(args.output).resolve().absolute()}", style="cyan")
with Progress(console=console, auto_refresh=True) as progress:
# files = Path(pth).rglob("*.png")
unique_info = {}
last = None
task = progress.add_task("[cyan]Extracting meta...", total=len(input_files) + 1)
for p in input_files:
im = Image.open(p)
parsed = parse_a111(im.info["parameters"], args.verbose)
if parsed != last:
unique_info[Path(p).stem] = parsed
last = parsed
progress.update(task, advance=1)
progress.refresh()
unique_info = json.dumps(unique_info, indent=4)
with open(args.output, "w") as f:
f.write(unique_info)
progress.update(task, advance=1)
progress.refresh()
console.print("\nProcessing completed!", style="bold green")
+213
View File
@@ -0,0 +1,213 @@
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()