home · contact · privacy
Switch from EXIF to PNG chunk tEXt metadata.
authorChristian Heller <c.heller@plomlompom.de>
Fri, 13 Sep 2024 01:34:47 +0000 (03:34 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Fri, 13 Sep 2024 01:34:47 +0000 (03:34 +0200)
browser.py
requirements.txt
stable.py
stable/core.py

index 1f25d2d69883c3eec59126f92d91f38e24a28e63..b38cf1037d02f59abf859421bf904c57508ec804 100755 (executable)
@@ -3,7 +3,8 @@
 from json import dump as json_dump, load as json_load
 from os.path import exists as path_exists, join as path_join, abspath
 from argparse import ArgumentParser
-from exiftool import ExifToolHelper  # type: ignore
+from PIL import Image
+from PIL.PngImagePlugin import PngImageFile
 import gi  # type: ignore
 gi.require_version('Gtk', '4.0')
 gi.require_version('Gdk', '4.0')
@@ -95,14 +96,15 @@ class ImgItem(FileItem):
                 for k in cached.keys():
                     setattr(self, k, cached[k])
 
-    def set_metadata(self, exif_tool, cache):
-        """Set instance attributes from 'Comment' EXIF tag, write to cache."""
-        for d in exif_tool.get_tags([self.full_path], ['Comment']):
-            for k, v in d.items():
-                if k.endswith('Comment'):
-                    gen_params = GenParams.from_str(v)
-                    for k, v_ in gen_params.as_dict.items():
-                        setattr(self, k, v_)
+    def set_metadata(self, cache):
+        """Set instance attributes from 'image file's GenParams PNG chunk."""
+        img = Image.open(self.full_path)
+        if isinstance(img, PngImageFile):
+            gen_params_as_str = img.text.get('generation_parameters', '')
+            if gen_params_as_str:
+                gen_params = GenParams.from_str(gen_params_as_str)
+                for k, v_ in gen_params.as_dict.items():
+                    setattr(self, k, v_)
         cached = {}
         for k in (k.lower() for k in GEN_PARAMS):
             cached[k] = getattr(self, k)
@@ -119,7 +121,6 @@ class MainWindow(Gtk.Window):
     recurse_dirs: bool
     per_row: int
     metadata: Gtk.TextBuffer
-    sort_order: list
     sort_store: Gtk.ListStore
     sort_selection: Gtk.SingleSelection
     prev_key: list
@@ -622,9 +623,8 @@ class MainWindow(Gtk.Window):
                     if '' == item.model:
                         to_set_metadata_on += [item]
                     self.gallery_store.append(item)
-            with ExifToolHelper() as et:
-                for item in to_set_metadata_on:
-                    item.set_metadata(et, cache)
+            for item in to_set_metadata_on:
+                item.set_metadata(cache)
 
         old_selection = self.gallery_selection.props.selected_item
         self.block_file_selection_updates = True
index e17a45990103857b1cfd491ad855185eb6cce085..e5365cf7409ee7f3ea92abed04d7645000f0a11d 100644 (file)
@@ -1,5 +1,4 @@
 # for stable.py
-pyexiftool
 pillow
 torch
 diffusers
index 694b2505449bbb94b21cf645bf0c51067b259c87..351add1b860d941c8863afaa274ed4513fb7ff9f 100755 (executable)
--- a/stable.py
+++ b/stable.py
@@ -20,9 +20,9 @@ def parse_args():
                         help='model filename (-P will pre prefixed, but may '
                         'also be full path on its own)')
     parser.add_argument('-o', '--output',
-                        help='output filename or path; if -q > 1, will insert '
-                        'incremented counter number; if no image file '
-                        'extension included, defaults to .png')
+                        help='output filename or path; if -q > 1 or name '
+                        'pre-existing, will insert incremented counter '
+                        'number; will append .png extension if not provided')
     parser.add_argument('-p', '--prompt',
                         help='textual guidance to image generation')
     parser.add_argument('-q', '--quantity', default=1, type=int,
@@ -105,6 +105,8 @@ def run():
         dir_path = dirname(args.output) if dirname(args.output) else '.'
         filename = basename(args.output)
         filename_sans_ext, ext = splitext(filename)
+        if ext not in {'', '.png', '.PNG'}:
+            raise Exception('Can only export to PNG.')
         ext = ext if ext else '.png'
         filename_with_ext = f'{filename_sans_ext}{ext}'
         start_at_idx = 0
index 3d8d4a0d4679f43ebf8286a60f9faccce734a934..4d0b05a808d6b779f1eaa6879a7add079f9eef22 100644 (file)
@@ -3,7 +3,7 @@ from logging import (Formatter as LogFormatter, captureWarnings,
 from diffusers import StableDiffusionPipeline
 from diffusers.utils import logging
 from torch import Generator, float16
-from exiftool import ExifToolHelper  # type: ignore
+from PIL.PngImagePlugin import PngInfo
 
 SAFETY_CHECKER_WARNING_PATTERN = 'You have disabled the safety checker'
 
@@ -51,6 +51,7 @@ class ImageMaker:
         return [s.__name__ for s in self.pipe.scheduler.compatibles]
 
     def gen_image_to(self, path):
+        """Create image and write as file with metadata to path."""
         if None in {self.gen_params.seed, self.gen_params.prompt,
                     self.gen_params.guidance, self.gen_params.height,
                     self.gen_params.width, self.gen_params.n_steps}:
@@ -63,7 +64,6 @@ class ImageMaker:
                           width=self.gen_params.width,
                           num_inference_steps=self.gen_params.n_steps,
                           ).images[0]
-        image.save(path)
-        with ExifToolHelper() as et:
-            et.set_tags([path], tags={'Comment': self.gen_params.to_str},
-                        params=['-overwrite_original'])
+        png_info = PngInfo()
+        png_info.add_text('generation_parameters', self.gen_params.to_str)
+        image.save(path, pnginfo=png_info)