mirror of
https://github.com/vladmandic/sdnext.git
synced 2026-10-11 22:49:59 +00:00
148 lines
5.4 KiB
Python
Executable File
148 lines
5.4 KiB
Python
Executable File
#!/usr/bin/env python
|
|
"""
|
|
Example:
|
|
cli/api-watermark.py --input ~/generative/Samples --wm-image ui/assets/logo-transparent.png --position center --wm-text "sdnext-test-invisible-watermark-string" --verify
|
|
"""
|
|
import os
|
|
import io
|
|
import time
|
|
import base64
|
|
import logging
|
|
import argparse
|
|
import requests
|
|
import urllib3
|
|
from PIL import Image
|
|
|
|
|
|
sd_url = os.environ.get('SDAPI_URL', "http://127.0.0.1:7860")
|
|
sd_username = os.environ.get('SDAPI_USR', None)
|
|
sd_password = os.environ.get('SDAPI_PWD', None)
|
|
|
|
logging.basicConfig(level = logging.INFO, format = '%(asctime)s %(levelname)s: %(message)s')
|
|
log = logging.getLogger(__name__)
|
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
|
|
|
|
def auth():
|
|
if sd_username is not None and sd_password is not None:
|
|
return requests.auth.HTTPBasicAuth(sd_username, sd_password)
|
|
return None
|
|
|
|
|
|
def get(endpoint: str, dct: dict | None = None):
|
|
req = requests.get(f'{sd_url}{endpoint}', json = dct, timeout=300, verify=False, auth=auth())
|
|
if req.status_code != 200:
|
|
return { 'error': req.status_code, 'reason': req.reason, 'url': req.url }
|
|
else:
|
|
return req.text.strip('"')
|
|
|
|
|
|
def post(endpoint: str, dct: dict | None = None):
|
|
req = requests.post(f'{sd_url}{endpoint}', json = dct, timeout=300, verify=False, auth=auth())
|
|
if req.status_code != 200:
|
|
return { 'error': req.status_code, 'reason': req.reason, 'url': req.url }
|
|
else:
|
|
return req.json()
|
|
|
|
|
|
def display(image: Image.Image):
|
|
try:
|
|
import subprocess
|
|
buf = io.BytesIO()
|
|
image.save(buf, format="PNG")
|
|
subprocess.run(["timg", "--pixelation", "sixel", "-"], input=buf.getvalue())
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def encode(img: str | Image.Image):
|
|
image = Image.open(img) if isinstance(img, str) else img
|
|
with io.BytesIO() as stream:
|
|
image.save(stream, 'PNG')
|
|
image.close()
|
|
values = stream.getvalue()
|
|
encoded = base64.b64encode(values).decode()
|
|
return encoded
|
|
|
|
|
|
def decode(encoding):
|
|
if encoding.startswith("data:image/"):
|
|
parts = encoding.split(";", 1)
|
|
if len(parts) == 2:
|
|
parts2 = parts[1].split(",", 1)
|
|
encoding = parts2[1] if len(parts2) == 2 else parts2[0]
|
|
return Image.open(io.BytesIO(base64.b64decode(encoding)))
|
|
|
|
|
|
def watermark(args): # pylint: disable=redefined-outer-name
|
|
if os.path.isdir(args.input):
|
|
filenames = [os.path.join(args.input, f) for f in os.listdir(args.input) if os.path.isfile(os.path.join(args.input, f))]
|
|
elif os.path.isfile(args.input):
|
|
filenames = [args.input]
|
|
else:
|
|
filenames = []
|
|
if not args.quiet:
|
|
log.info(f'Inputs: {filenames}')
|
|
stats = { 'verified': 0, 'failed': 0, 'skipped': 0 }
|
|
|
|
t0 = time.perf_counter()
|
|
for fn in filenames:
|
|
t1 = time.perf_counter()
|
|
try:
|
|
encoded = encode(fn)
|
|
except Exception as e:
|
|
log.error(f'Encode: file={fn}: {e}')
|
|
stats['skipped'] += 1
|
|
continue
|
|
options = { 'image': encoded }
|
|
if args.wm_text:
|
|
options['wm_text'] = args.wm_text
|
|
if args.wm_image:
|
|
options['wm_image'] = encode(args.wm_image)
|
|
if args.position:
|
|
options['position'] = args.position
|
|
response = post('/sdapi/v1/watermark', options)
|
|
image = decode(response)
|
|
t2 = time.perf_counter()
|
|
if args.output:
|
|
os.makedirs(args.output, exist_ok=True)
|
|
out = os.path.join(args.output, os.path.basename(fn))
|
|
image.save(out)
|
|
if not args.quiet:
|
|
log.info(f'Watermarked: input="{fn}" output="{out}" image={image.size} watermark="{args.wm_image}" time={t2-t1:.4f}')
|
|
|
|
if args.display and image is not None:
|
|
display(image)
|
|
|
|
if args.verify and args.wm_text:
|
|
t3 = time.perf_counter()
|
|
options = { 'image': encode(image), 'wm_text': '*' * len(args.wm_text) }
|
|
wm = get('/sdapi/v1/watermark', options)
|
|
t4 = time.perf_counter()
|
|
if wm == args.wm_text:
|
|
if not args.quiet:
|
|
log.info(f'Verified: input="{fn}" watermark="{wm}" time={t4-t3:.4f}')
|
|
stats['verified'] += 1
|
|
else:
|
|
log.error(f'Failed: input="{fn}" expected="{args.wm_text}" got="{wm}" time={t4-t3:.4f}')
|
|
stats['failed'] += 1
|
|
|
|
t5 = time.perf_counter()
|
|
log.info(f'Processed: images={len(filenames)} time={t5-t0:.4f} images/sec={len(filenames)/(t5-t0):.4f} avg={(t5-t0)/len(filenames):.4f} {stats}')
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description = 'api-watermark')
|
|
parser.add_argument('--input', required=True, default=None, help='input image or folder')
|
|
parser.add_argument('--output', required=False, default=None, help='output folder')
|
|
parser.add_argument('--wm-image', required=False, default=None, help='watermark image')
|
|
parser.add_argument('--wm-text', required=False, default=None, help='watermark text')
|
|
parser.add_argument('--position', required=False, default=None, help='watermark position')
|
|
parser.add_argument('--display', default=False, action='store_true', help='Display images')
|
|
parser.add_argument('--verify', default=False, action='store_true', help='Verify watermarks')
|
|
parser.add_argument('--quiet', default=False, action='store_true', help='Suppress debug output')
|
|
|
|
args = parser.parse_args()
|
|
log.info(f'api-watermark: {args}')
|
|
watermark(args)
|