Command-line (bash/GIMP) mass conversion and processing

This post was written by eli on July 31, 2009
Posted Under: gimp,Software

The purpose

I use GIMP a lot. I store the images in the native file format, XCF. Now I’m stuck with a lot of files I can’t see outside GIMP, but I don’t want to save those files as anything else, because I’ll lose all the layer data. Solution: Batch conversion to JPEG as a simple bash script to run from the command line.

The idea is to make a JPEG copy of the image as it’s seen when it’s opened with GIMP. For that reason, I’ve chosen to flatten the image by visible layers only, and to crop it to image size.

Also, I’ll show an example of how to massively fix images with a GIMP script.

2022 update: This post is really old. Nowadays ImageMagick supports XCF, so it’s possible to just go

$ convert this.xcf this.jpg

But this post can still be of use for more sophisticated tasks with GIMP.

The script

It looks like LISP and it looks like bash. In fact, they’re mixed.

cat <<EOF
(define (convert-xcf-to-jpeg filename outfile)
  (let* (
	 (image (car (gimp-file-load RUN-NONINTERACTIVE filename filename)))
	 (drawable (car (gimp-image-merge-visible-layers image CLIP-TO-IMAGE)))
    (file-jpeg-save RUN-NONINTERACTIVE image drawable outfile outfile .9 0 0 0 " " 0 1 0 1)
    (gimp-image-delete image) ; ... or the memory will explode

(gimp-message-set-handler 1) ; Messages to standard output

for i in *.xcf; do
  echo "(gimp-message \"$i\")"
  echo "(convert-xcf-to-jpeg \"$i\" \"${i%%.xcf}.jpg\")"

echo "(gimp-quit 0)"
} | gimp -i -b -

To try it out, simply execute the script from a directory containing several .xcf files. Be sure not have any .jpg files you care about in the same directory, because the outputs are with the same file names, just with the .jpg extension (old files are overwritten with no warning).

You will get kind-of-warning messages while the script is iterating, indicating which file is being processed. This is normal.

The concept of this script is simple. An ad-hoc LISP script is generated in the Bash block, which is enclosed by curly brackets. First we define the function, which converts one file. The bash script then creates calls to this function, by means of the (Bash) for-loop. All this is then fed into GIMP through standard input (piping).

Some LISP notes

I’m not really into LISP. So I ran into some trouble. These are my notes, so I won’t go through it again:

First, there’s the Script-Fu console, which was, well, sort-of helpful. The internal functions’ API can be found there as well.

As a LISP novice, I didn’t know the difference between “let” and “let*”. It turns out, that let* allows the use of previous assignments in the following ones, so this is what you get in the Script-Fu console:

> (let ( (x 2) (y x)) y)
Error: eval: unbound variable: x 

> (let* ( (x 2) (y x)) y)

It’s also worth to note, that the GIMP interpreter does not remember functions across different -b command-line arguments:

# First statement succeeds, second fails.
gimp -i -b '(define (myfun x y) (- x y))' -b '(myfun 2 3)'

# This works, because it's two statements in one execution (yuck!)
gimp -i -b '(define (myfun x y) (- x y)) (myfun 2 3)'

Mass processing of frames

As the title implies, I needed this to make adjustments on a video clip. It’s true that many video editors (Cinelerra included) have filters for that, but running a sequence of GIMP commands is probably stronger than any possible video editor.

So here’s a little script which runs some operations on a list of frames. This was useful, mainly because I wanted to run curves on a clip (and Cinelerra doesn’t have that operation. I wonder which video editor has).

cat <<EOF

(define (do-retouch filename outfile)
 (let* (
 (image (car (gimp-file-load RUN-NONINTERACTIVE filename filename)))
 (drawable (car (gimp-image-merge-visible-layers image CLIP-TO-IMAGE)))
 (gimp-colorize drawable 10 50 0)
 (gimp-curves-spline drawable HISTOGRAM-VALUE 6 #(0 0 147 44 255 122 ) )
 (gimp-hue-saturation drawable ALL-HUES 0 0 20)
 (plug-in-gauss RUN-NONINTERACTIVE image drawable 10 10 1)
 (file-png-save2 RUN-NONINTERACTIVE image drawable outfile outfile 0 9 0 0 0 0 0 0 1 )
 (gimp-image-delete image) ; ... or the memory will explode

(gimp-message-set-handler 1) ; Messages to standard output

for i in frame*.png; do
 echo "(gimp-message \"$i\")"
 echo "(do-retouch \"$i\" \"fixed/x_${i%%.png}.png\")"

echo "(gimp-quit 0)"
} | gimp -i -b

The script-Fu console was helpful in finding the function’s names, which are pretty obvious. I’ll only mention that running gimp-image-merge-visible-layers in this case is probably not necessary, but I suppose GIMP won’t waste too much time on merging a layer with itself.

As for the curves operation (gimp-curves-spline), I first tried to use curves traces which are saved by GIMP as they are being used in the GUI, but that turned out to be pretty complicated. So I went for the simple approach: Open the GUI, find the X-Y points on the graph, and copy them manually. The “6″ says that there are 6 numbers ahead (3 X-Y pairs), and then we have X0, Y0, X1, Y1, etc. The values go from 0 to 255. So it’s pretty trivial, and does exactly the same as the GUI.

Importing the frames to Cinelerra

Not that it’s directly relevant, but to import a bunch of frames into Cinelerra, use the mkframelist command line utility and load it like any file. For example,

$ ls *.png | mkframelist -r 30 > framelist

For 30 fps, and then load “framelist” to Cinelerra. Note that the paths in the list are absolute, so you can’t just move around the files.


Reader Comments

thanks so much, needed this. You rock dude!

Written By Benjamin on October 13th, 2009 @ 19:57

Thank you. A few slight modifications and I’m converting pnm files. Many thanks.

Written By David on December 14th, 2009 @ 03:27

./ line 5: syntax error near unexpected token `convert-xcf-to-jpeg’
./ line 5: `(define (convert-xcf-to-jpeg filename outfile)’

Written By bib on January 26th, 2010 @ 07:07

There are two weird things about your problem:

1. The offending line is within a “cat << EOF”, which means that bash shouldn’t parse these lines, but send them as is to gimp (to which the cat is piped)
2. convert-xcf-to-jpeg appears in line 4, not 5

It looks like you’re running another shell than bash. Or that you’ve modified the script significantly.

Written By eli on January 26th, 2010 @ 12:52

Thanks a lot!
(worked for me in Ubuntu Karmic Koala)

Written By Pedro on March 19th, 2010 @ 13:42

Thanks a lot!
Trying to alter the script for png saving but can’t get it to work.
Tried to replace all jpeg to png in the script and can’t find anything jpeg specific or missing from png save.
Any ideas?

Written By David on May 19th, 2010 @ 13:26

file-png-save takes completely different parameters. Open the script-fu console, click “Browse…” and type “png” in the text window. You’ll find file-png-save listed there. Have a look on the parameter list. Compare with file-jpeg-save and alter as necessary.

Written By eli on May 19th, 2010 @ 13:32

Hi. Thx for this post. It’s great. This is bash script which sends lisp commands to GIMP ( if I’m not wrong). Is it possible to make lisp sript which sends bash commands ?


Written By Adam on September 5th, 2010 @ 22:18

I’m not so good with lisp, as one can learn from the post itself.

A quick Google brought me to the run-shell-command function, but I don’t know if it’s included in GIMP or if this remark is even relevant. Hope someone pops up with a better answer…

Written By eli on September 5th, 2010 @ 22:26

thanks a lot
I’ve got about 75 files to convert
You just help me save a lot of time
thanks Eli

Written By biobio on January 29th, 2012 @ 22:21

shiny script it is, thank You!

Written By kir on July 12th, 2012 @ 19:13

Good tip my next project is about processing some images from bash to gimp

Written By Maria on January 12th, 2013 @ 09:25

Add a Comment

required, use real name
required, will not be published
optional, your blog address