Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Photobooth "look" - python script
#1
Python 
Hello
I asked Gemini GPT about how to replicate a colour chemical photobooth look in GIMP. Gemini explained the photobooth history, different lenses and chemical approaches and eventually wrote me a python script that creates the look.
Of course, the python script looks the part to an neophyte such as myself, but does not work either in python3 or Gimp python or as a filter. It does not show in GEGL either.
(I have yet to get a GPT sourced python script that works off the bat, so dev still have a career in front of them).
The funny thing is, the every time I ask the GPT to correct the script, it finds an "error" to correct, but none of these corrections ever make the code work.
The primary issue is that when I run the script in fu-script, I get loads of indentation error! Python indentation drives me crazy.
Therefore ... Does anyone know if there is a working "photobooth" GIMP script?
Can anyone help me debug this script for GIMP 3 linux  please ?
Code:
#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
import gi
import datetime

try:
   gi.require_version('Gimp', '3.0')
   gi.require_version('Gegl', '0.4')
   from gi.repository import Gimp, Gegl, GObject, GLib
except ValueError, e:
   sys.exit(1)


def apply_schneider_look(image, drawable):
   """Applies optical and chemical effects."""

   pdb = Gimp.get_pdb()

   # 1. Mirror

   drawable.transform_flip_simple(Gimp.OrientationType.HORIZONTAL,
                                  True, 0)

   # 2. Schneider Optical Effects
   # We use Gimp.ValueArray.new(count) for GIMP 3 compatibility

   dist_args = Gimp.ValueArray.new(3)
   dist_args.insert(0, GObject.Value(Gimp.Drawable, drawable))
   dist_args.insert(1, GObject.Value(GObject.TYPE_STRING,
                    'gegl:lens-distortion'))
   pdb.run_procedure('gimp-drawable-edit-gegl-config', dist_args)

   bloom_args = Gimp.ValueArray.new(3)
   bloom_args.insert(0, GObject.Value(Gimp.Drawable, drawable))
   bloom_args.insert(1, GObject.Value(GObject.TYPE_STRING, 'gegl:bloom'
                     ))
   pdb.run_procedure('gimp-drawable-edit-gegl-config', bloom_args)

   # 3. Chemical Color Shift

   cb_args = Gimp.ValueArray.new(11)
   cb_args.insert(0, GObject.Value(Gimp.Drawable, drawable))
   cb_args.insert(1, GObject.Value(GObject.TYPE_DOUBLE, -0.15))
   cb_args.insert(2, GObject.Value(GObject.TYPE_DOUBLE, 0.0))
   cb_args.insert(3, GObject.Value(GObject.TYPE_DOUBLE, 0.10))
   cb_args.insert(4, GObject.Value(GObject.TYPE_DOUBLE, 0.0))
   cb_args.insert(5, GObject.Value(GObject.TYPE_DOUBLE, 0.0))
   cb_args.insert(6, GObject.Value(GObject.TYPE_DOUBLE, 0.0))
   cb_args.insert(7, GObject.Value(GObject.TYPE_DOUBLE, 0.10))
   cb_args.insert(8, GObject.Value(GObject.TYPE_DOUBLE, 0.0))
   cb_args.insert(9, GObject.Value(GObject.TYPE_DOUBLE, -0.25))
   cb_args.insert(10, GObject.Value(GObject.TYPE_BOOLEAN, True))
   pdb.run_procedure('gimp-drawable-color-balance', cb_args)

   # 4. Silver Halide Grain

   grain_args = Gimp.ValueArray.new(3)
   grain_args.insert(0, GObject.Value(Gimp.Drawable, drawable))
   grain_args.insert(1, GObject.Value(GObject.TYPE_STRING,
                     'gegl:noise-rgb'))
   pdb.run_procedure('gimp-drawable-edit-gegl-config', grain_args)


def photobooth_main_proc(
   procedure,
   run_mode,
   image,
   drawables,
   args,
   data,
   ):
   Gimp.context_push()
   image.undo_group_start()

   # Constants

   (STRIP_W, STRIP_H) = (600, 1800)
   (MARGIN, FRAME_H) = (40, 400)

   new_image = Gimp.Image.new(STRIP_W, STRIP_H, Gimp.ImageBaseType.RGB)

   bg_color = Gimp.RGB()
   bg_color.set_parse('#F9F7F2')
   bg_layer = Gimp.Layer.new(
       new_image,
       'Paper Base',
       STRIP_W,
       STRIP_H,
       Gimp.Precision.U8_GAMMA,
       100,
       Gimp.LayerMode.NORMAL,
       )
   new_image.insert_layer(bg_layer, None, 0)
   Gimp.context_set_background(bg_color)
   bg_layer.fill(Gimp.FillType.BACKGROUND)

   y_offset = MARGIN
   for i in range(min(len(drawables), 4)):
       source_layer = drawables[i]
       new_frame = Gimp.Layer.new_from_drawable(source_layer,
               new_image)
       new_image.insert_layer(new_frame, None, -1)

       apply_schneider_look(new_image, new_frame)

       target_w = STRIP_W - MARGIN * 2
       scale_ratio = target_w / new_frame.get_width()
       new_frame.scale(target_w, int(new_frame.get_height()
                       * scale_ratio), True)
       new_frame.set_offsets(MARGIN, y_offset)
       y_offset += FRAME_H + 15

   # Mechanical Date Stamp

   stamp_color = Gimp.RGB()
   stamp_color.set_parse('#B22222')
   Gimp.context_set_foreground(stamp_color)
   date_str = datetime.datetime.now().strftime('PHOTO-ME - %d %b %Y'
           ).upper()

   # Text Layer logic for GIMP 3

   stamp_layer = Gimp.text_fontname(
       new_image,
       None,
       0,
       0,
       date_str,
       0,
       True,
       18,
       'Sans-Serif',
       )
   if stamp_layer:
       stamp_layer.transform_rotate_simple(Gimp.OrientationType.VERTICAL,
               True, 0, 0)
       stamp_layer.set_offsets(STRIP_W - 35, STRIP_H - 450)
       stamp_layer.set_opacity(70)

   Gimp.Display.new(new_image)
   image.undo_group_end()
   Gimp.context_pop()
   Gimp.displays_flush()

   return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS,
           GLib.Error())


class PhotoboothPro(Gimp.PlugIn):

   def do_query_procedures(self):
       return ['python-fu-photobooth-pro']

   def do_create_procedure(self, name):
       procedure = Gimp.ImageProcedure.new(self, name,
               Gimp.PDBProcType.PLUGIN, photobooth_main_proc, None)
       procedure.set_image_types('RGB*')
       procedure.set_documentation('Schneider Photobooth Pro',
                                   'Full analog strip recreation',
                                   name)
       procedure.set_menu_label('Schneider Photobooth Pro...')
       procedure.add_menu_path('<Image>/Filters/Artistic')
       procedure.set_attribution('Gemini', 'Gemini', '2026')
       return procedure


Gimp.main(PhotoboothPro.__gtype__, sys.argv)
Reply
#2
I can see right at the start - the shebang should be #!/usr/bin/env python3   and if the AI gets that wrong what else.  I have not seen any sympathy from coders to get involved in a fixing AI code, but who knows,  maybe one someone will help.
Also quote...The primary issue is that when I run the script in fu-script  Not script-fu, it is python.

Post an example of the type of image effect, I did a search for Schneider Photobooth and nothing apart from a load of social media rubbish.

Maybe as an alternative there is the gimp_gmic_qt plugin http://www.gmic.eu  Comes with many (600) look-up-tables (LUT) See if anything there matches your requirments.

   
Reply
#3
Hi,
The code is very rough. It's output isn't very useful as is, but it runs now. I had some help with Google AI and and ChatGPT.

Code:
#!/usr/bin/env python3
import sys
import gi
import datetime

"""
# I use this when debugging. Any 'print()' statements get sent to 'file'.

# Trace errors by recording them to a file.
# If the file doesn't exist, create it.
import time
file = sys.stderr = sys.stdout = open("D:\\error.txt", 'a')
_start = f"\nPhotoBoothPro, {time.ctime()}"
_end = "_" * (80 - len(_start))
print(f"{_start}{_end}")
"""

gi.require_version('Gimp', '3.0')
gi.require_version('Gegl', '0.4')
from gi.repository import Gegl, Gimp, GLib   # noqa

STRIP_W, STRIP_H = 600, 1800
MARGIN, FRAME_H = 40, 400


def run_filter(layer, filter_, q, mode, opacity):
   """
   layer: Gimp.Layer
       Receive filter output.
   filter_: Gimp.DrawableFilter
   q: iterable
       [(property str, property value)]
   mode: Gimp.LayerMode
       Identify the layer blend mode with an enum (int).
   opacity: float
       .0 to 100.
   """
   config = filter_.get_config()

   for q1 in q:
       config.set_property(*q1)

   filter_.set_blend_mode(mode)
   filter_.set_opacity(opacity)
   filter_.update()
   layer.append_filter(filter_)
   return filter_


def make_filter(layer, s):
   """
   Create a "Gimp.DrawableFilter" by string.

   layer: Gimp.Layer
       The filter name, e.g. 'oil-paint'.
   """
   print(f"Making filter: {s}")
   return Gimp.DrawableFilter.new(layer, f'gegl:{s}', "")


def do_color_balance(
   drawable,
   transfer_mode,
   preserve_lum,
   cyan_red,
   magenta_green,
   yellow_blue
):
   """Adjust color balance of a drawable."""
   procedure = Gimp.get_pdb().lookup_procedure('gimp-drawable-color-balance')
   config = procedure.create_config()
   config.set_property('drawable', drawable)
   config.set_property('transfer-mode', transfer_mode)
   config.set_property('preserve-lum', preserve_lum)
   config.set_property('cyan-red', cyan_red)
   config.set_property('magenta-green', magenta_green)
   config.set_property('yellow-blue', yellow_blue)
   result = procedure.run(config)
   success = result.index(0)
   return success


def apply_schneider_look(image, drawable):
   """Applies optical and chemical effects."""
   # 1. Mirror
   drawable.transform_flip_simple(Gimp.OrientationType.HORIZONTAL, True, 0)

   # Prepare arguments for Lens Distortion.
   filter_ = make_filter(drawable, 'lens-distortion')

   # 2. Schneider Optical Effects
   # Use the default filter settings, '[]', for Lens Distortion.
   run_filter(
       drawable,
       filter_,
       [],
       Gimp.LayerMode.NORMAL,
       100.
   )

   filter_ = make_filter(drawable, 'bloom')

   # Use the default filter settings, '[]', for Bloom.
   run_filter(
       drawable,
       filter_,
       [],
       Gimp.LayerMode.NORMAL,
       100.
   )

   # 3. Chemical Color Balance
   preserve_lum = True
   cyan_red = -0.15
   magenta_green = 0.10
   yellow_blue = -0.25

   do_color_balance(
       drawable,
       Gimp.TransferMode.SHADOWS,
       preserve_lum,
       cyan_red, magenta_green, yellow_blue
   )
   do_color_balance(
       drawable,
       Gimp.TransferMode.HIGHLIGHTS,
       preserve_lum,
       cyan_red, magenta_green, yellow_blue
   )
   do_color_balance(
       drawable,
       Gimp.TransferMode.MIDTONES,
       preserve_lum,
       cyan_red, magenta_green, yellow_blue
   )

   # 4. Silver Halide Grain
   filter_ = make_filter(drawable, 'noise-rgb')
   run_filter(
       drawable,
       filter_,
       [],
       Gimp.LayerMode.NORMAL,
       100.
   )


def photobooth_main_proc(
   procedure,
   run_mode,
   image,
   drawables,
   args,
   data,
):
   Gimp.context_push()
   image.undo_group_start()

   new_image = Gimp.Image.new(STRIP_W, STRIP_H, Gimp.ImageBaseType.RGB)

   Gimp.Display.new(new_image)     #

   bg_color = Gegl.Color.new('#F9F7F2')
   bg_layer = Gimp.Layer.new(
       new_image,
       'Paper Base',
       STRIP_W,
       STRIP_H,
       Gimp.ImageType.RGBA_IMAGE,
       100.,
       Gimp.LayerMode.NORMAL,
   )
   new_image.insert_layer(bg_layer, None, 0)
   Gimp.context_set_background(bg_color)
   bg_layer.fill(Gimp.FillType.BACKGROUND)

   y_offset = MARGIN

   for i in range(min(len(drawables), 4)):
       source_layer = drawables[i]
       new_frame = Gimp.Layer.new_from_drawable(source_layer, new_image)

       new_image.insert_layer(new_frame, None, -1)
       apply_schneider_look(new_image, new_frame)

       target_w = STRIP_W - MARGIN * 2
       scale_ratio = target_w / new_frame.get_width()

       new_frame.scale(
           target_w, int(new_frame.get_height() * scale_ratio), True
       )
       new_frame.set_offsets(MARGIN, y_offset)
       y_offset += FRAME_H + 15

   # Mechanical Date Stamp
   stamp_color = Gegl.Color.new('#B22222')
   date_str = datetime.datetime.now().strftime('PHOTO-ME - %d %b %Y').upper()
   Gimp.context_set_foreground(stamp_color)

   # This should work but doesn't.
   font = Gimp.Font.get_by_name('Sans-serif')

   # Text Layer logic for GIMP 3
   stamp_layer = Gimp.TextLayer.new(
       new_image,
       date_str,
       font,
       18.0,
       Gimp.Unit.pixel()
   )
   new_image.insert_layer(stamp_layer, None, 0)

   if stamp_layer:
       stamp_layer.transform_rotate_simple(
           Gimp.OrientationType.VERTICAL, True, 0, 0
       )
       # stamp_layer.set_offsets(STRIP_W - 35, STRIP_H - 450)  Needs work.
       stamp_layer.set_opacity(70.)

   Gimp.Display.new(new_image)
   image.undo_group_end()
   Gimp.context_pop()
   Gimp.displays_flush()
   return procedure.new_return_values(
       Gimp.PDBStatusType.SUCCESS, GLib.Error()
   )


class PhotoboothPro(Gimp.PlugIn):

   def do_query_procedures(self):
       return ['python-fu-photobooth-pro']

   def do_create_procedure(self, name):
       procedure = Gimp.ImageProcedure.new(
           self, name, Gimp.PDBProcType.PLUGIN, photobooth_main_proc, None
       )
       procedure.set_image_types('RGB*')
       procedure.set_documentation(
           'Schneider Photobooth Pro',
           'Full analog strip recreation',
           name
       )
       procedure.set_menu_label('Schneider Photobooth Pro...')
       procedure.add_menu_path('<Image>/Filters/Artistic')
       procedure.set_attribution('Gemini', 'Gemini', '2026')
       return procedure


Gimp.main(PhotoboothPro.__gtype__, sys.argv)


Attached Files
.zip   PhotoboothPro.zip (Size: 3.01 KB / Downloads: 13)
Reply
#4
(01-09-2026, 10:41 AM)rich2005 Wrote: I have not seen any sympathy from coders to get involved in a fixing AI code, but who knows,  maybe one someone will help.
If I was a coder, I certainly would not be very sympathetic.  What you have done is confirm to me what I have always suspected ... AI coding is all about talking the talk and not really walking the walk. If I had coded this with very little I know, I would have got an equally unsatisfactory result.
The goal of the script is to replicate an ektachrome, old photobooth with the 4 vertical, passport pictures. Asking Gemini about how to do it, produced a very convincing history of the photobooth and a script to replicate these "technologies" in GIMP.
I had very little faith in the script working, but I thought that someone could confirm if it is complete junk or not.
Thank you for your thoughts.

(01-10-2026, 02:45 PM)gasMask Wrote: Hi,
The code is very rough. It's output isn't very useful as is, but it runs now. I had some help with Google AI and and ChatGPT.
Thank you for taking a look at it. 
I wrote the script for linux & mac but I notice that there is a Windows path. I presume that the windows does have any impact in the script, as I can't get it to run. Currently, I have your script installedon a mac in 
/Users/admin/Library/Application Support/GIMP/3.0/scripts
which is set in the preferences. After Gimp restart it is not appearing in the filter. When I copy your script into the GIMP Python console, I let loads of indent errors.
Were you ableto get it working?
Thank you
Reply
#5
(01-11-2026, 09:11 PM)chlowden007 Wrote:
(01-09-2026, 10:41 AM)rich2005 Wrote: I have not seen any sympathy from coders to get involved in a fixing AI code, but who knows,  maybe one someone will help.
If I was a coder, I certainly would not be very sympathetic.  What you have done is confirm to me what I have always suspected ... AI coding is all about talking the talk and not really walking the walk. If I had coded this with very little I know, I would have got an equally unsatisfactory result.
The goal of the script is to replicate an ektachrome, old photobooth with the 4 vertical, passport pictures. Asking Gemini about how to do it, produced a very convincing history of the photobooth and a script to replicate these "technologies" in GIMP.
I had very little faith in the script working, but I thought that someone could confirm if it is complete junk or not.
Thank you for your thoughts.

(01-10-2026, 02:45 PM)gasMask Wrote: Hi,
The code is very rough. It's output isn't very useful as is, but it runs now. I had some help with Google AI and and ChatGPT.
Thank you for taking a look at it. 
I wrote the script for linux & mac but I notice that there is a Windows path. I presume that the windows does have any impact in the script, as I can't get it to run. Currently, I have your script installedon a mac in 
/Users/admin/Library/Application Support/GIMP/3.0/scripts
which is set in the preferences. After Gimp restart it is not appearing in the filter. When I copy your script into the GIMP Python console, I let loads of indent errors.
Were you ableto get it working?
Thank you

Hi,
The Window's path won't effect the plug-in's in any way because the path is inside a comment.

The zipped plug-in folder needs to sit within a Gimp recognized "plug-in" folder. You can find the recognized plug-in folders in the Gimp's Edit/Preferences dialog.

I'm not a console developer, but I'm sure copying and pasting sections of code doesn't work there.

The plug-in runs for me, but in order to make it useful, you will need to understand Python, and make necessary alterations to the code with a code editor.
Reply
#6
Python scripts go into the plug-ins folder, not into scripts.

Unpack and copy the folder plus script into plug-ins.
Make the .py file exectutable.
Restart GIMP
Reply
#7
Well,   another way....   As I mentioned in #2 the gimp_gmic_qt  plugin http://www.gmic.eu has several filters that might be suitable.  There is an ektachrome filter that might work, adjust the parameters to suit. Copy those to clipboard.

   

That can go in a script-fu along with filters to add a border and make a vertical stack. Sorry, no text at the moment. Just for info the script-fu attached. Registers in the tools menu. For Gimp 3 . Remember this does need the gmic plugin.

   


Attached Files
.scm   for-gmic.scm (Size: 1.09 KB / Downloads: 8)
Reply
#8
(01-12-2026, 08:42 AM)MrsP-from-C Wrote: Python scripts go into the plug-ins folder, not into scripts.

Unpack and copy the folder plus script into plug-ins.
Make the .py file exectutable.
Restart GIMP

Thank you to you all. It helps to put the script / plugin in the right folder.
Initially I could not find it in the filters. In the GIMP python page, I found the required modules code and copied it in to the file and it worked.
https://docs.gimp.org/3.0/en/gimp-using-...orial.html
I then noticed that the code was already there but not in exactly the same way ... should have worked
I find that the script works much better than expected. I had to double the script's canvas length to 3000 pixels as the 4x 2:3 pictures I have were overlapping.
   

There was also too much RGB noise for my taste.
The best part is that the effects are not rendered, so they are all adjustable, which is much better than the "filmstrip" filter that has no adjustments possible after the render.
   

I have to say that this not bad at all, for a first try. Of course it needs rework and it is way below the promises that Gemini promised, but it has done much of the dull "stuff". Thank you again for all you help.


Attached Files
.py   PhotoboothPro.py (Size: 6.73 KB / Downloads: 11)
Reply


Forum Jump: