diff --git a/CHANGELOG.md b/CHANGELOG.md index edd6151e9..9ce678621 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Change Log for SD.Next -## Update for 2025-07-23 +## Update for 2025-07-24 -### Highlights for 2025-07-23 +### Highlights for 2025-07-24 Feature highlights include: - **ModernUI** layout redesign which should make it more user friendly and easier to navigate @@ -29,7 +29,7 @@ For details, see [ChangeLog](https://github.com/vladmandic/automatic/blob/master [ReadMe](https://github.com/vladmandic/automatic/blob/master/README.md) | [ChangeLog](https://github.com/vladmandic/automatic/blob/master/CHANGELOG.md) | [Docs](https://vladmandic.github.io/sdnext-docs/) | [WiKi](https://github.com/vladmandic/automatic/wiki) | [Discord](https://discord.com/invite/sd-next-federal-batch-inspectors-1101998836328697867) -### Details for 2025-07-23 +### Details for 2025-07-24 - **License** - SD.Next [license](https://github.com/vladmandic/sdnext/blob/dev/LICENSE.txt) switched from **aGPL-v3.0** to **Apache-v2.0** @@ -114,6 +114,7 @@ For details, see [ChangeLog](https://github.com/vladmandic/automatic/blob/master - **Batch** warn on unprocessable images and skip operations on errors so that other images can still be processed - **Metadata** improved parsing and detect foreign metadata detect ComfyUI images + detect InvokeAI images - **Detailer** add `expert` mode where list of detailer models can be converted to textbox for manual editing see [docs](https://vladmandic.github.io/sdnext-docs/Detailer/) for more information - **Detailer** add option to merge multiple results from each detailer model diff --git a/modules/images.py b/modules/images.py index 7ba9f9c50..619f73d4e 100644 --- a/modules/images.py +++ b/modules/images.py @@ -232,17 +232,75 @@ def safe_decode_string(s: bytes): return None -def parse_comfy_metadata(data: str): - res = '' - try: - dct = json.loads(data) - version = dct['extra'].get('frontendVersion', None) if 'extra' in dct else '' - nodes = dct.get('nodes', []) if 'nodes' in dct else [] - if 'ComfyUI' in data: - res = f"App: ComfyUI | Version: {version} | Nodes: {len(nodes)}" - except Exception as e: - shared.log.error(f'Error parsing ComfyUI metadata: {e}') - return res +def parse_comfy_metadata(data: dict): + def parse_workflow(): + res = '' + try: + txt = data.get('workflow', {}) + dct = json.loads(txt) + nodes = len(dct.get('nodes', [])) + version = dct.get('extra', {}).get('frontendVersion', 'unknown') + if version is not None: + res = f" | Version: {version} | Nodes: {nodes}" + except: + pass + return res + + def parse_prompt(): + res = '' + try: + txt = data.get('prompt', {}) + dct = json.loads(txt) + for val in dct.values(): + inp = val.get('inputs', {}) + if 'model' in inp: + model = inp.get('model', None) + if isinstance(model, str) and len(model) > 0: + res += f" | Model: {model} | Class: {val.get('class_type', '')}" + except: + pass + return res + + workflow = parse_workflow() + prompt = parse_prompt() + if len(workflow) > 0 or len(prompt) > 0: + parsed = f'App: ComfyUI{workflow}{prompt}' + shared.log.info(f'Image metadata: {parsed}') + return parsed + return '' + + +def parse_invoke_metadata(data: dict): + def parse_metadtaa(): + res = '' + try: + txt = data.get('invokeai_metadata', {}) + dct = json.loads(txt) + if 'app_version' in dct: + version = dct['app_version'] + if isinstance(version, str) and len(version) > 0: + res += f" | Version: {version}" + except: + pass + return res + + metadata = parse_metadtaa() + if len(metadata) > 0: + parsed = f'App: InvokeAI{metadata}' + shared.log.info(f'Image metadata: {parsed}') + return parsed + + +def parse_novelai_metadata(data: dict): + geninfo = '' + if data.get("Software", None) == "NovelAI": + try: + dct = json.loads(data["Comment"]) + sampler = sd_samplers.samplers_map.get(dct["sampler"], "Euler a") + geninfo = f"{data["Description"]} Negative prompt: {dct["uc"]} Steps: {dct["steps"]}, Sampler: {sampler}, CFG scale: {dct["scale"]}, Seed: {dct["seed"]}, Clip skip: 2, ENSD: 31337" + except Exception as e: + pass + return geninfo def read_info_from_image(image: Image, watermark: bool = False): @@ -286,19 +344,9 @@ def read_info_from_image(image: Image, watermark: bool = False): if isinstance(val, bytes): # decode bytestring items[key] = safe_decode_string(val) - if "prompt" in items: - geninfo += parse_comfy_metadata(items["prompt"]) - if "workflow" in items: - geninfo += parse_comfy_metadata(items["workflow"]) - if items.get("Software", None) == "NovelAI": - try: - json_info = json.loads(items["Comment"]) - sampler = sd_samplers.samplers_map.get(json_info["sampler"], "Euler a") - geninfo = f"""{items["Description"]} -Negative prompt: {json_info["uc"]} -Steps: {json_info["steps"]}, Sampler: {sampler}, CFG scale: {json_info["scale"]}, Seed: {json_info["seed"]}, Size: {image.width}x{image.height}, Clip skip: 2, ENSD: 31337""" - except Exception as e: - errors.display(e, 'novelai image parser') + geninfo += parse_comfy_metadata(items) + geninfo += parse_invoke_metadata(items) + geninfo += parse_novelai_metadata(items) for key in ['exif', 'ExifOffset', 'JpegIFOffset', 'JpegIFByteCount', 'ExifVersion', 'icc_profile', 'jfif', 'jfif_version', 'jfif_unit', 'jfif_density', 'adobe', 'photoshop', 'loop', 'duration', 'dpi']: # remove unwanted tags items.pop(key, None)