mirror of https://github.com/vladmandic/automatic
parent
18568db41c
commit
400d284711
13
CHANGELOG.md
13
CHANGELOG.md
|
|
@ -1,8 +1,8 @@
|
|||
# Change Log for SD.Next
|
||||
|
||||
## Update for 2026-03-24
|
||||
## Update for 2026-03-25
|
||||
|
||||
### Highlights for 2026-03-4
|
||||
### Highlights for 2026-03-25
|
||||
|
||||
This release brings massive code refactoring to modernize codebase and removal of some obsolete features. Leaner & Faster!
|
||||
And since its a bit quieter period when it comes to new models, notable additions would be : *FireRed-Image-Edit* *SkyWorks-UniPic-3* and new *Anima-Preview*
|
||||
|
|
@ -18,7 +18,7 @@ But also many smaller quality-of-life improvements - for full details, see [Chan
|
|||
|
||||
[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) | [Sponsor](https://github.com/sponsors/vladmandic)
|
||||
|
||||
### Details for 2026-03-24
|
||||
### Details for 2026-03-25
|
||||
|
||||
- **Models**
|
||||
- [Google Flash 3.1 Image](https://ai.google.dev/gemini-api/docs/models/gemini-3-flash-preview) a.k.a. *Nano Banana 2*
|
||||
|
|
@ -69,6 +69,9 @@ But also many smaller quality-of-life improvements - for full details, see [Chan
|
|||
- **Cuda** `torch==2.10` removed support for `rtx1000` series and older GPUs
|
||||
use following before first startup to force installation of `torch==2.9.1` with `cuda==12.6`:
|
||||
> `set TORCH_COMMAND='torch==2.9.1 torchvision==0.24.1 torchaudio==2.9.1 --index-url https://download.pytorch.org/whl/cu126'`
|
||||
- **Ipex** update to `torch==2.11`
|
||||
- **ROCm/Linux** update to `torch==2.11` with `rocm==7.2`
|
||||
- **OpenVINO** update to `torch==2.11` and `openvino==2026.0`
|
||||
- **UI**
|
||||
- legacy panels **T2I** and **I2I** are disabled by default
|
||||
you can re-enable them in *settings -> ui -> hide legacy tabs*
|
||||
|
|
@ -168,7 +171,9 @@ But also many smaller quality-of-life improvements - for full details, see [Chan
|
|||
- improve video generation progress tracking
|
||||
- handle startup with bad `scripts` more gracefully
|
||||
- thread-safety for `error-limiter`, thanks @awsr
|
||||
- add `lora` support for flux2-klein
|
||||
- add `lora` support for flux2-klein
|
||||
- fix `lora` change when used with `sdnq`
|
||||
- multiple `sdnq` fixes
|
||||
|
||||
## Update for 2026-02-04
|
||||
|
||||
|
|
|
|||
6
TODO.md
6
TODO.md
|
|
@ -7,6 +7,7 @@
|
|||
- Add notes: **Enso**
|
||||
- Tips: **Color Grading**
|
||||
- Regen: **Localization**
|
||||
- AGENTS.md
|
||||
|
||||
## Internal
|
||||
|
||||
|
|
@ -20,6 +21,7 @@
|
|||
- Engine: `TensorRT` acceleration
|
||||
- Feature: Auto handle scheduler `prediction_type`
|
||||
- Feature: Cache models in memory
|
||||
- Feature: JSON image metadata
|
||||
- Validate: Control tab add overrides handling
|
||||
- Feature: Integrate natural language image search
|
||||
[ImageDB](https://github.com/vladmandic/imagedb)
|
||||
|
|
@ -28,7 +30,6 @@
|
|||
- Feature: Video tab add full API support
|
||||
- Refactor: Unify *huggingface* and *diffusers* model folders
|
||||
- Refactor: [GGUF](https://huggingface.co/docs/diffusers/main/en/quantization/gguf)
|
||||
- Refactor: move sampler options from settings to config
|
||||
- Reimplement `llama` remover for Kanvas, pending end-to-end review of `Kanvas`
|
||||
|
||||
## OnHold
|
||||
|
|
@ -55,6 +56,7 @@ TODO: Investigate which models are diffusers-compatible and prioritize!
|
|||
|
||||
- [Chroma Zeta](https://huggingface.co/lodestones/Zeta-Chroma): Image and video generator for creative effects and professional filters
|
||||
- [Chroma Radiance](https://huggingface.co/lodestones/Chroma1-Radiance): Pixel-space model eliminating VAE artifacts for high visual fidelity
|
||||
- [Bria FIBO](https://huggingface.co/briaai/FIBO): Fully JSON based
|
||||
- [Liquid](https://github.com/FoundationVision/Liquid): Unified vision-language auto-regressive generation paradigm
|
||||
- [Lumina-DiMOO](https://huggingface.co/Alpha-VLLM/Lumina-DiMOO): Foundational multi-modal generation and understanding via discrete diffusion
|
||||
- [nVidia Cosmos-Predict-2.5](https://huggingface.co/nvidia/Cosmos-Predict2.5-2B): Physics-aware world foundation model for consistent scene prediction
|
||||
|
|
@ -113,8 +115,6 @@ TODO: Investigate which models are diffusers-compatible and prioritize!
|
|||
|
||||
### Not Planned
|
||||
|
||||
- [Bria FIBO](https://huggingface.co/briaai/FIBO): Fully JSON based
|
||||
- [Bria FiboEdit](https://github.com/huggingface/diffusers/commit/d7a1c31f4f85bae5a9e01cdce49bd7346bd8ccd6): Fully JSON based
|
||||
- [LoRAdapter](https://github.com/CompVis/LoRAdapter): Not recently updated
|
||||
- [SD3 UltraEdit](https://github.com/HaozheZhao/UltraEdit): Based on SD3
|
||||
- [PowerPaint](https://github.com/open-mmlab/PowerPaint): Based on SD15
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 9d584a1bdc0c2aca614aa0e1e34e4374c3aa779d
|
||||
Subproject commit e8374c5b5e2b97961cf6ca9fa72a90b0dea479aa
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import io
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import piexif
|
||||
|
|
@ -8,26 +9,24 @@ from modules.logger import log
|
|||
from modules.image.watermark import get_watermark
|
||||
|
||||
|
||||
debug = log.trace if os.environ.get("SD_METADATA_DEBUG", None) is not None else lambda *args, **kwargs: None
|
||||
|
||||
|
||||
def safe_decode_string(s: bytes):
|
||||
remove_prefix = lambda text, prefix: text[len(prefix):] if text.startswith(prefix) else text # pylint: disable=unnecessary-lambda-assignment
|
||||
remove_prefix = lambda text, prefix: text[len(prefix):] if text.startswith(prefix) else text # pylint: disable=unnecessary-lambda-assignment
|
||||
s = remove_prefix(s, b'UNICODE')
|
||||
s = remove_prefix(s, b'ASCII')
|
||||
s = remove_prefix(s, b'\x00')
|
||||
# Detect UTF-16LE: even length and every other byte (odd positions) is 0x00 in the first ~20 bytes
|
||||
if len(s) >= 2 and len(s) % 2 == 0 and all(b == 0 for b in s[1:min(len(s), 20):2]):
|
||||
try:
|
||||
val = s.decode('utf-16-le', errors='strict')
|
||||
val = re.sub(r'[\x00-\x09]', '', val).strip()
|
||||
if val:
|
||||
return val
|
||||
except Exception:
|
||||
pass
|
||||
for encoding in ['utf-8', 'utf-16', 'utf-16-be', 'ascii', 'latin_1', 'cp1252', 'cp437']: # try different encodings
|
||||
for encoding in ["utf-16-le", "utf-16-be", "utf-8", "utf-16", "ascii", "latin_1", "cp1252", "cp437"]: # try different encodings
|
||||
try:
|
||||
if encoding == "utf-16-le":
|
||||
if not (len(s) >= 2 and len(s) % 2 == 0 and all(b == 0 for b in s[1 : min(len(s), 20) : 2])): # not utf-16-le
|
||||
continue
|
||||
val = s.decode(encoding, errors="strict")
|
||||
val = re.sub(r'[\x00-\x09]', '', val).strip() # remove remaining special characters
|
||||
if len(val) == 0: # remove empty strings
|
||||
val = None
|
||||
debug(f'Metadata: decode="{val}" encoding="{encoding}"')
|
||||
return val
|
||||
except Exception:
|
||||
pass
|
||||
|
|
@ -68,6 +67,7 @@ def parse_comfy_metadata(data: dict):
|
|||
if len(workflow) > 0 or len(prompt) > 0:
|
||||
parsed = f'App: ComfyUI{workflow}{prompt}'
|
||||
log.info(f'Image metadata: {parsed}')
|
||||
debug(f'Metadata: comfy="{parsed}"')
|
||||
return parsed
|
||||
return ''
|
||||
|
||||
|
|
@ -90,6 +90,7 @@ def parse_invoke_metadata(data: dict):
|
|||
if len(metadata) > 0:
|
||||
parsed = f'App: InvokeAI{metadata}'
|
||||
log.info(f'Image metadata: {parsed}')
|
||||
debug(f'Metadata: invoke="{parsed}"')
|
||||
return parsed
|
||||
return ''
|
||||
|
||||
|
|
@ -101,9 +102,25 @@ def parse_novelai_metadata(data: dict):
|
|||
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'
|
||||
debug(f'Metadata: novelai="{geninfo}"')
|
||||
return geninfo
|
||||
except Exception:
|
||||
pass
|
||||
return geninfo
|
||||
return ''
|
||||
|
||||
|
||||
def parse_xmp_metadata(data: dict):
|
||||
# Extract XMP dc:subject tags into a readable field
|
||||
geninfo = ''
|
||||
xmp_raw = data.get("xmp")
|
||||
if xmp_raw and isinstance(xmp_raw, (str, bytes)):
|
||||
xmp_str = xmp_raw if isinstance(xmp_raw, str) else xmp_raw.decode("utf-8", errors="replace")
|
||||
xmp_tags = re.findall(r"<rdf:li>([^<]+)</rdf:li>", xmp_str)
|
||||
if xmp_tags:
|
||||
geninfo = f"XMP Tags: {', '.join(xmp_tags)}"
|
||||
debug(f'Metadata: xmp="{geninfo}"')
|
||||
return geninfo
|
||||
return ''
|
||||
|
||||
|
||||
def read_info_from_image(image: Image.Image, watermark: bool = False) -> tuple[str, dict]:
|
||||
|
|
@ -131,6 +148,7 @@ def read_info_from_image(image: Image.Image, watermark: bool = False) -> tuple[s
|
|||
log.error(f'Error loading EXIF data: {e}')
|
||||
exif = {}
|
||||
for _key, subkey in exif.items():
|
||||
debug(f'Metadata EXIF: key="{_key}" subkey="{subkey}" type="{type(subkey)}"')
|
||||
if isinstance(subkey, dict):
|
||||
for key, val in subkey.items():
|
||||
if isinstance(val, bytes): # decode bytestring
|
||||
|
|
@ -145,27 +163,23 @@ def read_info_from_image(image: Image.Image, watermark: bool = False) -> tuple[s
|
|||
items[ExifTags.TAGS[key]] = val
|
||||
elif val is not None and key in ExifTags.GPSTAGS:
|
||||
items[ExifTags.GPSTAGS[key]] = val
|
||||
|
||||
if watermark:
|
||||
wm = get_watermark(image)
|
||||
if wm != '':
|
||||
debug(f'Metadata: watermark="{wm}"')
|
||||
# geninfo += f' Watermark: {wm}'
|
||||
items['watermark'] = wm
|
||||
|
||||
for key, val in items.items():
|
||||
if isinstance(val, bytes): # decode bytestring
|
||||
items[key] = safe_decode_string(val)
|
||||
debug(f'Metadata: key="{key}" value="{items[key]}"')
|
||||
|
||||
geninfo += parse_comfy_metadata(items)
|
||||
geninfo += parse_invoke_metadata(items)
|
||||
geninfo += parse_novelai_metadata(items)
|
||||
|
||||
# Extract XMP dc:subject tags into a readable field
|
||||
xmp_raw = items.get('xmp')
|
||||
if xmp_raw and isinstance(xmp_raw, (str, bytes)):
|
||||
xmp_str = xmp_raw if isinstance(xmp_raw, str) else xmp_raw.decode('utf-8', errors='replace')
|
||||
xmp_tags = re.findall(r'<rdf:li>([^<]+)</rdf:li>', xmp_str)
|
||||
if xmp_tags:
|
||||
items['xmp_tags'] = ', '.join(xmp_tags)
|
||||
geninfo += parse_xmp_metadata(items)
|
||||
|
||||
for key in ['exif', 'ExifOffset', 'JpegIFOffset', 'JpegIFByteCount', 'ExifVersion', 'icc_profile', 'jfif', 'jfif_version', 'jfif_unit', 'jfif_density', 'adobe', 'photoshop', 'loop', 'duration', 'dpi', 'xmp']: # remove unwanted tags
|
||||
items.pop(key, None)
|
||||
|
|
@ -177,6 +191,8 @@ def read_info_from_image(image: Image.Image, watermark: bool = False) -> tuple[s
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
debug(f'Metadata geninfoi: "{geninfo}"')
|
||||
debug(f'Metadata items: "{items}"')
|
||||
return geninfo, items
|
||||
|
||||
|
||||
|
|
|
|||
2
wiki
2
wiki
|
|
@ -1 +1 @@
|
|||
Subproject commit 99f4e13d03191b5269b869c71283d7fcf9c98f60
|
||||
Subproject commit 7abb07dc95bdb2c1869e2901213f5c82b46905c3
|
||||
Loading…
Reference in New Issue