How to automate conversion of images - bash

I can convert an image like this:
convert -resize 50% foo.jpg foo_50.jpg
How can I automate such a command to convert all the images in a folder?
You can assume every image has .jpg extension.
A solution easily adaptable to automate the conversion of all the images inside the subdirectories of the working directory is preferable.

You can use a for loop with pattern expansion:
for img in */*.jpg ; do
convert -resize 50% "$img" "${img%.jpg}"_50.jpg
done
${variable%pattern} removes the pattern from the right side of the $variable.

You can use find -exec:
find -type f -name '*.jpg' -exec \
bash -c 'convert -resize 50% "$0" "${0%.jpg}"_50.jpg' {} \;
find -type f -name '*.jpg' finds all .jpg files (including those in subdirectories) and hands it to the command after -exec, where it can be referenced using {}.
Because we want to use parameter expansion, we can't use -exec convert -resize directly; we have to call bash -c and supply {} as a positional parameter to it ($0 inside the command). \; marks the end of the -exec command.

You can also try this (less elegant) one-liner using ls+awk:
ls *.jpg | awk -F '.' '{print "convert -resize 50% "$0" "$1"_50.jpg"}' | sh
this assumes that all the .jpg files are in the current directory. before running this, try to remove the | sh and see what is printed on the screen.

Related

Bash convert resize recursively preserving filenames

Have images in subfolders that need to be limited in size (720 max width or 1100 max height). Their filenames must be preserved. Started with:
for img in *.jpg; do filename=${img%.*}; convert -resize 720x1100\> "$filename.jpg" "$filename.jpg"; done
which works within each directory, but have a lot of subfolders with these images. Tried find . -iname "*.jpg" -exec cat {} but it did not create a list as expected.
This also didn't work:
grep *.jpg | while read line ; do `for img in *.jpg; do filename=${img%.jpg}; convert -resize 720x1100\> "$filename.jpg" "$filename.jpg"`; done
Neither did this:
find . -iname '*jpg' -print0 | while IFS= read -r -d $'\0' line; do convert -resize 720x1100\> $line; done
which gives me error message "convert: no images defined." And then:
find . -iname '*.jpg' -print0 | xargs -0 -I{} convert -resize 720x1100\> {}
gives me the same error message.
It seems you're looking for simply this:
find /path/to/dir -name '*.jpg' -exec mogrify -resize 720x1100\> {} \;
In your examples, you strip the .jpg extension, and then you add it back. No need to strip at all, and that simplifies things a lot.
Also, convert filename filename is really the same as mogrify filename. mogrify is part of ImageMagick, it's useful for modifying files in-place, overwriting the original file. convert is useful for creating new files, preserving originals.
Since all of the subdirectories are two levels down, found this worked:
for img in **/*/*.jpg ; do filename=${img%.*}; convert -resize 720x1100\> "$filename.jpg" "$filename.jpg"; done
Thanks to #pjh for getting me started. This also worked:
shopt -s globstar ; for img in */*/*.jpg ; do filename=${img%.*}; convert -resize 720x1100\> "$filename.jpg" "$filename.jpg"; done
But I got the error message "-bash: shopt: globstar: invalid shell option name" but all of the images larger than specified were resized with filenames preserved.

How do I recursively find files with specific names and join using ImageMagick in Terminal?

I have created an ImageMagick command to join images with certain names:
convert -append *A_SLIDER.jpg *B_SLIDER.jpg out.jpg
I have lots of folders with files named *A_SLIDER.jpg and *B_SLIDER.jpg next to each other (only ever one pair in a directory).
I would like to recursively search a directory with many folders and execute the command to join the images.
If it is possible to name the output image based on the input images that would be great e.g.
=> DOGS_A_SLIDER.jpg and DOGS_B_SLIDER.jpg would combine to DOGS_SLIDER.jpg
Something like this, but back up first and try on a sample directory only!
#!/bin/bash
find . -name "*A_SLIDER*" -execdir bash -c ' \
out=$(ls *A_SLIDER*);
out=${out/_A/}; \
convert -append "*A_SLIDER*" "*B_SLIDER*" $out' \;
Find all files containing the letters "A_SLIDER" and go to the containing directory and start bash there. While you are there, get the name of the file, and remove the _A part to form the output filename. Then execute ImageMagick convert with the _A_ and the corresponding _B_ files to form the output file.
Or, a slightly more concise suggestion from #gniourf_gniourf... thank you.
#!/bin/bash
find . -name "*A_SLIDER.jpg" -type f -execdir bash -c 'convert -append "$1" "${1/_A_/_B_}" "${1/_A/}"' _ {} \;
The "find" command will recursively search folders:
$ find . -name "*.jpg" -print
That will display all the filenames. You might instead want "-iname" which does case-insensitive filename matching.
You can add a command line with "-exec", in which "{}" is replaced by the name of the file. You must terminate the command line with "\;":
$ find . -name "*.jpg" -exec ls -l {} \;
You can use sed to edit the name of a file:
$ echo DOGS_A_SLIDER.jpg | sed 's=_.*$=='
DOGS
Can you count on all of your "B" files being named the same as the corresponding "A" files? That is, you will not have "DOGS_A_SLIDER.jpg" and "CATS_A_SLIDER.jpg" in the same directory. If so, something like the following isn't everything you need, but will contribute to your solution:
$ find . -type f -name "*.jpg" -exec "(echo {} | sed 's=_.*==')" \;
That particular sed script will do the wrong thing if you have any directory names with underscores in them.
"find . -type f" finds regular files; it runs modestly faster than without the -type. Use "-d" to find directories.

Converting all .pdf files in folder tree to .png

I'm on Ubuntu, and I have a tree of folders containing .pdf files. I need to convert each one to a .png format. The bash script I am currently using is:
for f in $(find ./polkadots -name 'image.pdf'); do
convert -transparent white -fuzz 10% $f image.png;
done
I have tested the for loop by itself, and it works (it produces a list of all the .pdf files under the ./polkadots folder that I need to convert):
for f in $(find ./polkadots -name 'image.pdf'); do
echo "$f";
done
I have tested the imagemagic convert command by itself, and it works (it converts a single file in my current directory from .pdf to .png):
convert -transparent white -fuzz 10% image.pdf image.png
However, when I combine them... the console sits and thinks for a while, and then concludes.. but no files have been created or changed, and no error messages are produced. What am I doing wrong?
EDIT: The new .png files are being created, but they are being created in my current directory, instead of in the sub-directory where the .pdf was found. How do I fix this?
Try using find alone. No need to use a loop.
I haven't tested this command but it should work.
find ./polkadots -name 'image.pdf' -exec convert -transparent white -fuzz 10% {} image.png \; -print
The -print at the end is optional. I prefer to see which files have been modified.
Maybe you can find output option in convert command directly, which can export the png file to your expect folder. Anyway follow your idea, here is the updated code:
find ./polkadots -type f -name "image.pdf" |while read line
do
dir=${line%/*}
convert -transparent white -fuzz 10% $line image.png
mv image.png ${dir}/image.png
done
If you need convert all pdf files under polkadots folder, try this:
find ./polkadots -type f -name "*.pdf" |while read line
do
dir=${line%/*}
file=${line##*/}
file=${file%.*}
convert -transparent white -fuzz 10% $line ${file}.png
echo mv ${file}.png ${dir}/${file}.png
done
If you are using bash 4+, you should use globstar instead of find
#!/bin/bash
shopt -s globstar
for f in ./polkadots/**/image.pdf; do
convert -transparent white -fuzz 10% "$f" "${f%/*}/image.png"
done
If you're using an older bash, your original answer is close to fine, but has a few bugs/potential bugs,
If you directories have spaces in them, your original script will case errors, so use the | while read -r -d '' f syntax.
Don't put a ; at the end of command lines
Quote all variables to prevent expansion problems
As you pointed out, main issue in your case was not specifying destination dir, so you can use ${f%/*} parameter expansion to get the dir (this will delete everything including and after the last / in $f, then put the / back and append the filename like below.
Edited
#!/bin/bash
find ./polkadots -name "image.pdf" -print0 | while read -r -d '' f; do
convert -transparent white -fuzz 10% "$f" "${f%/*}/image.png"
done

(mogrify) ln -s creating copies of files

Running the following script:
for i in $(find dir -name "*.jpg"); do
ln -s $i
done
incredibly makes symbolic links for 90% of the files and makes of a copy of the remaining 10%. How's that possible?
Edit: what's happening after is relevant:
Those are links to images that I rotate through mogrify e.g.
mogrify -rotate 90 link_to_image
It seems like mogrify on a link silently makes a copy of the image, debatable choice, but that's what it is.
Skip the first paragraph if you want to know more about processing of files with spaces in the names
It was not clear, what is the root of the problem and our assumption was that the problem is in the spaces in the filenames: that files that have them are not processed correctly.
The real problem was mogrify that applied to the created links, processed them and changed with real files.
No about spaces in filenames.
Processing of files with spaces in their names
That is because of spaces in names of the files.
You can write something like this:
find dir -name \*.jpg | while IFS= read i
do
ln -s "$i"
done
(IFS= is used here to avoiding stripping of leading spaces, thanks to #Alfe for the tip).
Or use xargs.
If it is possible that names contain "\n", it's better to use print0:
find dir -name \*.jpg -print0 | xargs -0 -N1 ln -s
Of course, you can use other methods also, for example:
find dir -name '*.jpg' -exec ln -s "{}" \;
ln -s "$(find dir -name '*.jpg')" .
(Imagemagick) mogrify applied on a link delete the link and makes a copy of the image
Try with single quotes:
find dir -name '*.jpg' -exec ln -s "{}" \;

How can I convert JPG images recursively using find?

Essentially what I want to do is search the working directory recursively, then use the paths given to resize the images. For example, find all *.jpg files, resize them to 300x300 and rename to whatever.jpg.
Should I be doing something along the lines of $(find | grep *.jpg) to get the paths? When I do that, the output is directories not enclosed in quotation marks, meaning that I would have to insert them before it would be useful, right?
I use mogrify with find.
Lets say, I need everything inside my nested folder/another/folder/*.jpg to be in *.png
find . -name "*.jpg" -print0|xargs -I{} -0 mogrify -format png {}
&& with a bit of explaination:
find . -name *.jpeg -- to find all the jpeg's inside the nested folders.
-print0 -- to print desired filename withouth andy nasty surprises (eg: filenames space seperated)
xargs -I {} -0 -- to process file one by one with mogrify
and lastly those {} are just dummy file name for result from find.
You can use something like this with GNU find:
find . -iname \*jpg -exec /your/image/conversion/script.sh {} +
This will be safer in terms of quoting, and spawn fewer processes. As long as your script can handle the length of the argument list, this solution should be the most efficient option.
If you need to handle really long file lists, you may have to pay the price and spawn more processes. You can modify find to handle each file separately. For example:
find . -iname \*jpg -exec /your/image/conversion/script.sh {} \;

Resources