243

Does anyone know a good way to batch-convert a bunch of PNGs into JPGs in linux? (I'm using Ubuntu).

A png2jpg binary that I could just drop into a shell script would be ideal.

13 Answers 13

358

Your best bet would be to use ImageMagick.

I am not an expert in the actual usage, but I know you can pretty much do anything image-related with this!

An example is:

convert image.png image.jpg

which will keep the original as well as creating the converted image.

As for batch conversion, I think you need to use the Mogrify tool which is part of ImageMagick.

Keep in mind that this overwrites the old images.

The command is:

mogrify -format jpg *.png
11
  • 7
    Awesome, that's exactly what I was after and will be using again. By the way, just to clarify as I didn't realise this is what you meant: convert is used to generate a separate output file, mogrify is used to modify the original image.
    – nedned
    Nov 16, 2009 at 4:11
  • 8
    To convert PNG's with transparent background, use the following command: mogrify -format jpg -background black -flatten *.png
    – hyperknot
    Jun 26, 2012 at 18:54
  • 8
    @KevinCox on my linux box after mogrify -format jpeg img.png I have 2 files and file img.* reports one png, the original untouched, and a new jpeg one. So mogrify does not overwrite original files in this case.
    – neurino
    Aug 28, 2013 at 6:54
  • 10
    From mogrify documentation: "This tool is similiar to convert except that the original image file is overwritten (unless you change the file suffix with the -format option) with any changes you request."
    – Janko
    Sep 18, 2014 at 17:07
  • 6
    If you need to compress your png's more use mogrify -quality 75 -format jpg *.png
    – Flatron
    Jun 20, 2016 at 7:43
123

I have a couple more solutions.

The simplest solution is like most already posted. A simple bash for loop.

for i in *.png ; do convert "$i" "${i%.*}.jpg" ; done

For some reason I tend to avoid loops in bash so here is a more unixy xargs approach, using bash for the name-mangling.

ls -1 *.png | xargs -n 1 bash -c 'convert "$0" "${0%.*}.jpg"'

The one I use. It uses GNU Parallel to run multiple jobs at once, giving you a performance boost. It is installed by default on many systems and is almost definitely in your repo (it is a good program to have around).

ls -1 *.png | parallel convert '{}' '{.}.jpg'

The number of jobs defaults to the number of CPU cores you have. I found better CPU usage using 3 jobs on my dual-core system.

ls -1 *.png | parallel -j 3 convert '{}' '{.}.jpg'

And if you want some stats (an ETA, jobs completed, average time per job...)

ls -1 *.png | parallel --eta convert '{}' '{.}.jpg'

There is also an alternative syntax if you are using GNU Parallel.

parallel convert '{}' '{.}.jpg' ::: *.png

And a similar syntax for some other versions (including debian).

parallel convert '{}' '{.}.jpg' -- *.png
11
  • 2
    +1 for correct bash string expansion in the for, if I could give you another upvote for mentioning parallel, I would. There's one typo, however - you need a done at the end of that for loop. Also, for the parallel stuff, you could avoid using that ls and pipe with a construct like: parallel -j 3 --eta convert '{}' '{.}.jpg' ::: *.png (see here)
    – evilsoup
    Jan 28, 2013 at 3:04
  • Fixed typo. That is a cool syntax that I didn't know of. I don't know which one I like better for probably the same reason I prefer not to use loops in bash. I put it the solution because it is probably the more "proper" way but I'll probably stick with the ls method for myself because it makes more sense to me.
    – Kevin Cox
    Jan 28, 2013 at 14:04
  • 1
    ...although it should be noted that that syntax only works on GNU parallel. The parallel that's packaged in some linux distros (like Debian & Ubuntu) is actually a different version with a slightly different syntax (use -- rather than :::) - and even then, it frustratingly lacks some of the features of GNU parallel.
    – evilsoup
    Jan 28, 2013 at 14:17
  • 1
    If you have a directory with more than 10,000 png images in it... the ls command will likely fail. So, this command works in those situations: find . -type f -name '*.png' | parallel --eta convert '{}' '{.}.jpg'
    – Ahi Tuna
    Oct 3, 2018 at 13:45
  • 1
    I've learnt so much from this answer.
    – Nae
    Jun 1, 2021 at 11:29
30

The convert command found on many Linux distributions is installed as part of the ImageMagick suite. Here's the bash code to run convert on all PNG files in a directory and avoid that double extension problem:

for img in *.png; do
    filename=${img%.*}
    convert "$filename.png" "$filename.jpg"
done
4
  • 8
    According to the man page for convert: "The convert program is a member of the ImageMagick(1) suite of tools."
    – nedned
    Nov 16, 2009 at 4:06
  • 1
    You are correct. For some reason I thought it was part of a different library. Either way the code I posted above is the correct way to automate batch conversion within a directory.
    – Marcin
    Nov 16, 2009 at 4:17
  • 3
    You can use bash expansion to improve that command like: for f in *.png; do convert "$f" "${f/%png/jpg}"; done
    – evilsoup
    Jan 28, 2013 at 2:57
  • just remember it's case sensitive. my camera name it as *.JPG and didn't realize this in first instance.
    – tsenapathy
    Mar 27, 2016 at 1:07
21

tl;dr

For those who just want the simplest commands:

Convert and keep original files:

mogrify -format jpg *.png

Convert and remove original files:

mogrify -format jpg *.png && rm *.png

Batch Converting Explained

Kinda late to the party, but just to clear up all of the confusion for someone who may not be very comfortable with cli, here's a super dumbed-down reference and explanation.

Example Directory

bar.png
foo.png
foobar.jpg

Simple Convert

Keeps all original png files as well as creates jpg files.

mogrify -format jpg *.png

Result

bar.png
bar.jpg
foo.png
foo.jpg
foobar.jpg

Explanation

  • mogrify is part of the ImageMagick suite of tools for image processing.
    • mogrify processes images in place, meaning the original file is overwritten, with the exception of the -format option. (From the site: This tool is similar to convert except that the original image file is overwritten (unless you change the file suffix with the -format option))
  • The - format option specifies that you will be changing the format, and the next argument needs to be the type (in this case, jpg).
  • Lastly, *.png is the input files (all files ending in .png).

Convert and Remove

Converts all png files to jpg, removes original.

mogrify -format jpg *.png && rm *.png

Result

bar.jpg
foo.jpg
foobar.jpg

Explanation

  • The first part is the exact same as above, it will create new jpg files.
  • The && is a boolean operator. In short:
    • When a program terminates, it returns an exit status. A status of 0 means no errors.
    • Since && performs short circuit evaluation, the right part will only be performed if there were no errors. This is useful because you may not want to delete all of the original files if there was an error converting them.
  • The rm command deletes files.

Fancy Stuff

Now here's some goodies for the people who are comfortable with the cli.

If you want some output while it's converting files:

for i in *.png; do mogrify -format jpg "$i" && rm "$i"; echo "$i converted to ${i%.*}.jpg"; done

Convert all png files in all subdirectories and give output for each one:

find . -iname '*.png' | while read i; do mogrify -format jpg "$i" && rm "$i"; echo "Converted $i to ${i%.*}.jpg"; done

Convert all png files in all subdirectories, put all of the resulting jpgs into the all directory, number them, remove original png files, and display output for each file as it takes place:

n=0; find . -iname '*.png' | while read i; do mogrify -format jpg "$i" && rm "$i"; fn="all/$((n++)).jpg"; mv "${i%.*}.jpg" "$fn"; echo "Moved $i to $fn"; done
6
  • Probably the best answer provided you get rid of the while read part (replace it or remove it all together)... Oct 26, 2015 at 12:07
  • @don_crissti, what's wrong with while read? Oct 26, 2015 at 13:47
  • It's error prone (unless you're 100% sure you're dealing with sane file names) and slow (like in very, very, very slow). Oct 26, 2015 at 14:01
  • What is the default JPG quality, and how can file timestamps be preserved? Oct 17, 2017 at 7:24
  • @DanDascalescu The methods above (except the last one) will preserve filenames, but replace their extension, so timestamped files should be okay (always make a copy and test first). According to ImageMagick, "The default is to use the estimated quality of your input image if it can be determined, otherwise 92" (imagemagick.org/script/command-line-options.php#quality) Quality can be specified with -quality <number> where <number> is 1 to 100. Oct 17, 2017 at 21:31
9

The actual "png2jpg" command you are looking for is in reality split into two commands called pngtopnm and cjpeg, and they are part of the netpbm and libjpeg-progs packages, respectively.

png2pnm foo.png | cjpeg > foo.jpeg
9
find . -name "*.png" -print0 | xargs -0 mogrify -format jpg -quality 50
1
  • 2
    Thanks for a deep/recursive directory one-line solution which leaves the resulting *.jpg files next to the original *.png files, shows how to reduce file size/quality and doesn't break because of any odd characters in directory or file name.
    – Joel Purra
    Dec 28, 2014 at 15:21
5

my quick solution for i in $(ls | grep .png); do convert $i $(echo $i.jpg | sed s/.png//g); done

4
  • 2
    This has got to be one of the ugliest, most convoluted command-lines I've ever seen
    – evilsoup
    Jan 28, 2013 at 2:56
  • 1
    @evilsoup honestly, this is elegant for shell scripts. Claiming it is convoluted isn't fair.
    – Max Howell
    Nov 5, 2013 at 17:31
  • 8
    @MaxHowell man. No. Here would be an elegant version of this: for f in ./*.png; do convert "$f" "${f%.*}.jpg"; done. That avoids the completely unnecessary ls, grep and sed calls (and echo, but IIRC that's a bash builtin and so will have no/very little performance impact), and gets rid of two pipes and two subshells, and involves less typing. It's even slightly more portable, since not all versions of ls are safe to parse.
    – evilsoup
    Nov 6, 2013 at 12:08
  • @evilsoup I stand corrected! Good job.
    – Max Howell
    Nov 6, 2013 at 14:58
4

For batch processing:

for img in *.png; do
  convert "$img" "$img.jpg"
done

You will end up with file names like image1.png.jpg though.

This will work in bash, and maybe bourne. I don't know about other shells, but the only difference would likely be the loop syntax.

4

Many years too late, there's a png2jpeg utility specifically for this purpose, which I authored.

Adapting the code by @Marcin:

#!/bin/sh

for img in *.png
do
    filename=${img%.*}
    png2jpeg -q 95 -o "$filename.jpg" "$filename.png"
done
0
1

This is what I use to convert when the files span more than one directory. My original one was TGA to PNG

find . -name "*.tga" -type f | sed 's/\.tga$//' | xargs -I% convert %.tga %.png

The concept is you find the files you need, strip off the extension then add it back in with xargs. So for PNG to JPG, you'd change the extensions and do one extra thing to deal with alpha channels namely setting the background (in this example white, but you can change it) then flatten the image

find . -name "*.png" -type f | sed 's/\.png$//' | xargs -I% convert %.png -background white -flatten  %.jpg
1

If your PNG is transparent, try adding a black bg before converting:

mogrify -format jpg -background black -flatten *.png

or a white bg:

mogrify -format jpg -background white -flatten *.png
1
  • 1
    Thanks. Last comment but saved my from going down a rabbit hole figuring out why ? >> mogrify: unable to open image 'jpg:': No such file or directory @ error/blob.c/OpenBlob/3496.
    – naim5am
    Apr 23, 2020 at 21:20
0

Here's the same bash solution but with ffmpeg converting:

for i in *.png ; do ffmpeg -i "$i" "${i%.*}.jpg" ; done
4
  • is it possible to compress the image, jpg to jpg or jpeg to jpeg? Apr 6, 2021 at 8:49
  • Should work. Please try this and let me know. Just change the line's .png to .jpg. Apr 6, 2021 at 17:13
  • just to confirm is it this command ffmpeg -i input.jpg output.jpg Apr 7, 2021 at 13:20
  • That command you mentioned should recompress a single jpg. Apr 7, 2021 at 20:03
0

I made this script,for jpg to png it works well:

#!/bin/bash                                                                    
clear
# Remove Spaces in the names                        
for f in *\ *; do mv "$f" "${f// /_}"; done
# Number of files to convert                         
files=$(find . -maxdepth 1 -name '*.jpg')
total=$(wc -l <<< "$files")
files=$(find . -maxdepth 1 -prune -name '*.JPG')
total=$(( $total + $(wc -l <<< "$files") ))
files=$(find . -maxdepth 1 -prune -name '*.jpeg')
total=$(( $total + $(wc -l <<< "$files") ))
files=$(find . -maxdepth 1 -prune -name '*.JPEG')
total=$(( $total + $(wc -l <<< "$files") ))
echo "There is  $total JPG files"
# Convert                                               
echo "    Convert is going on..."
 for img in *.*; do
       filename=${img%.*}
       form3="${img: -3}"
       form4="${img: -4}"
     if [[ $form3 =~ "JPG" ]] || [[ $form3 =~ "jpg" ]];
        then
        convert "$filename.$form3" "$filename.png"
        echo "  $img  ===>  ${img%.$form3}.png"
     elif [[ $form4 =~ "JPEG" ]] || [[ $form4 =~ "jpeg" ]];
        then
        convert "$filename.$form4" "$filename.png"
        echo "  $img  ==>  ${img%.$form4}.png"
     else echo
    fi
 done
 echo " Convert finished !"
# Remove                                                                      
echo " Removing originals files"
echo
shopt -s nocaseglob
 for i in *.jpg; do
rm -v $i
 done
  for i in *.jpeg; do
rm -v $i
  done
echo
ls --color=auto -C
exit
2
  • How is this better than accepted answer given 13 years ago?
    – Toto
    Jul 13, 2022 at 16:54
  • To elaborate on @Toto's answer: doesn't mogrify (as mentioned in that accepted answer) do this for you? It's a nice effort though :)
    – MiG
    Jul 13, 2022 at 22:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .