18

Let's say I have a bunch of photos, all with correct EXIF information, and the photos are randomly named (because of a problem I had). I have a little program called jhead which gives me the below output:

$ jhead IMG_9563.JPG

File name    : IMG_9563.JPG
File size    : 638908 bytes
File date    : 2011:02:03 20:25:09
Camera make  : Canon
Camera model : Canon PowerShot SX210 IS
Date/Time    : 2011:02:03 20:20:24
Resolution   : 1500 x 2000
Flash used   : Yes (manual)
Focal length :  5.0mm  (35mm equivalent: 29mm)
CCD width    : 6.17mm
Exposure time: 0.0080 s  (1/125)
Aperture     : f/3.1
Focus dist.  : 0.29m
ISO equiv.   : 125
Exposure bias: -1.67
Whitebalance : Manual
Light Source : Daylight
Metering Mode: pattern
Exposure Mode: Manual

Now I need to rename all the photos in the folder in the next format:

001.JPG
002.JPG
003.JPG
...

Where the minor number would be the older image, and the maximum the newer.

I'm not so good scripting, so I'm asking for help.

I think a bash script is enough, but if you feel more comfortable, you can write a python script.

I thought in something like:

$ mv IMG_9563.JPG `jhead IMG_9563.JPG | grep date`

but I don't know how to do that for all the files at once.

5
  • I don't understand the minor/maximum sentence and the preceding example. Could you clarify this? Feb 6, 2011 at 22:47
  • That minor/maximum file names is one step up. Because, once I have the files named in a way they represent which is older and newer, I can easily rename to 001.jpg, 002.jpg, etc with another program, although it'd be better to do that with another script.
    – Tomas
    Feb 6, 2011 at 23:01
  • The "renaming table" would be ls *.JPG | wc > rename And then I'd have to use a script rename to XXX.JPG
    – Tomas
    Feb 6, 2011 at 23:03
  • Sory, is not wc, I forgot the one to order by name.
    – Tomas
    Feb 6, 2011 at 23:07
  • The command is sort.
    – Tomas
    Feb 8, 2011 at 1:06

8 Answers 8

38

Just found out here that jhead can do it all for you! :)

jhead -autorot -nf%Y-%m-%d_%H-%M-%S *.jpg
3
  • 7
    Yes! But be careful of that particular format-string, because it'll give two files taken in the same second the same name. %i (or %03i, specifically) will give a sequence number, as requested in the original question. Combining both might not be a bad idea.
    – mattdm
    Mar 21, 2011 at 17:21
  • 2
    Just to respond to mattdm's comment - the ''jhead'' manual suggests it will append an extra number or letter (1,2,3 or a,b,c etc.) to duplicates (identical timestamp) automatically.
    – John U
    Sep 18, 2018 at 14:16
  • 2
    Depending on your shell, you might be better 'quoting' the format string Oct 27, 2021 at 9:46
10

You can to it for all files using a for loop (in the shell/in a shell-script):

for i in *.JPG; do
  j=`jhead "$i" | grep date | sed 's/^File date[^:]\+: \(.\+\)$/\1/'`.jpg
  echo mv -i "$i" "$j"
done

This is just a very basic outline. Delete echo when you have verified that everything works as expected.

0
3

exiv2 would be an alternative for manipulating, that allows a very simple syntax:

exiv2 is a program to read and write Exif, IPTC, XMP metadata and image comments and can read many vendor makernote tags. The program optionally con‐ verts between Exif tags, XMP properties and IPTC datasets

So this would rename all jpegs in the current folder:

for i in *.JPG; do exiv2 -v -r '%Y%m%d.%H%M%S.:basename:' rename "$i"; done

If you also want to add geo information, you could use exivtool:

exiftool '-filename<${gpslatitude;} ${gpslongitude} ${datetimeoriginal}' -d "%Y-%m-%d %H.%M.%S%%-c.%%e" *.JPG
0

If someone needs more complex rename, for example to include key exposure values into filename, here is my small script. It renames jpeg files to something like this: NightSky_2014.08.27_22.30.05_NX20_F2.8_f20.0mm_20s_ISO800.jpg.

#!/bin/bash

for s in *.jpg *.JPG
do
 echo $s
 x=`jhead "$s" | \
 awk 'BEGIN { cmt=""; }
/Camera model/   { c=$4$5$6;} 
/Exposure time:/ { e=$3; 
                   if (e==int(e)) e=int(e); 
                   if (e<1) {e=int(0.5+1/e); e="1T" e "s";} else { e=e "s"; } 
                 }
/ISO equiv./     { iso="ISO" $4; } 
/Focal length/   { f="f" $4; } 
/Date.Time /     { d=$3 "_" $4; gsub(":",".",d); }
/Aperture /      { ap=$3; gsub("f/","F",ap); } 
/Comment  /      { cmt=$3 "_"; }
END { print cmt d "_" c "_" ap "_" f "_" e "_" iso ".jpg"; }'`

 echo mv $s $x
 mv $s $x
 echo =====
done
0

I liked the code posted by maxschlepzig but had difficulties with the output nonetheless.

The problem was the space in the resulting filename (between the date string and time string). While trivial for anyone using a GUI, it makes file handling on the command-line somewhat more difficult.

Here the 'sed' command has been substantially changed to four separate 'sed' operations in favour of the previous monolithic argument. To suit myself, the following also changes the file to normal 644 permissions.

for i in *.JPG ; do
  chmod 644 $i
  j=`jhead "$i" | grep ^Date/Time | sed -e 's/^Date\/Time[ ]\+: //;s/:/-/g;s/ /_/g':/-/g;s/ /_/g'`.jpg
  echo mv -i "$i" "$j"
  # mv -i "$i" "$j"
done
2
  • 2
    Welcome to Stack Exchange.  (1) I understand sed pretty well, so I basically understand what you are trying to do.  But our goal at Stack Exchange is not to hand out fish sandwiches or write thousands of one-off solutions to trivially distinct questions; our goal is to teach people how to fish (i.e., teach people, including the current questioner and future readers, how to solve their own problems).  To that end, your answer would be better if you explained what you are endeavoring.  (Please do not respond in comments; edit your answer to make it clearer.)  … (Cont’d) May 31, 2015 at 22:08
  • (Cont’d) …  (2) Your command fails because the quotes aren’t balanced.  Please do not post untested code. May 31, 2015 at 22:08
0

Because it's easier to deal with (imho), I wrote myself a Ruby script:

require 'fileutils'

ARGV.each { |file|
  if File.exist?(file) && !File.directory?(file)
    exif = `exiftool "#{file}" | grep -E "(Error|Create Date)"`
    if exif.strip.size > 0
      exif = exif.split("\n")[0].split(/\s+/)
      if exif[0] != "Error"
        # Change the target format here
        filename = exif[3].gsub(":", "-") + " " + 
                   exif[4].gsub(":", ".") + 
                   File.extname(file)
        if filename != file && !File.exist?(filename)
          FileUtils::mv(file, File.dirname(file) + "/" + filename)
        end
      end
    end
  end
}

What does this do?

  1. Iterates over all files passed as parameters (e.g. *.JPG).

    I checked that it handles RAW files and videos correctly. It should work with everything exiftool can deal with.

  2. Does not do anything if a file

    • does not exist,
    • is a directory,
    • does not have an EXIF date, or
    • exiftool reports an error, or
    • a file with the target filename already exists.

That makes it quite robust. In particular, no files can vanish (silently) as is the case with some of the other answers.

0

I like @Kevin's solution, since I wanted to keep the original name as well (avoiding problem with images taken in the same second), here it is my solution:

for i in *.JPG; do jhead -nf%Y%m%d_%H%M%S_"$(basename "$i" .JPG)" "$i" ; done
0

First posting by newbie... First bash script... I liked Libor/HalosGhost solution above as it included more details in the rename. But after testing, duplicate file names would end up losing files. So I added a counter tag to end of filename for easy reference and collision prevention. I am sure someone here can improve on this but thought it may help.

My apologies posting the code. I am having difficulties with the interface but if someone could point me to correct way of doing things, that would be great.

#!/bin/bash

y=1
for s in *.jpg *.JPG
do
 echo $s
 x=`jhead "$s" | \
 awk 'BEGIN { cmt=""; }
/Camera model/   { c=$4$5$6;}
/Exposure time:/ { e=$3;
                   if (e==int(e)) e=int(e);
                   if (e<1) {e=int(0.5+1/e); e="1T" e "s";} else { e=e "s"; }
                 }
/ISO equiv./     { iso="ISO" $4; }
/Focal length/   { f="f" $4; }
/Date.Time /     { d=$3 "_" $4; gsub(":","",d); }
/Aperture /      { ap=$3; gsub("f/","F",ap); }
/Comment  /      { cmt=$3 "_"; }
END { print cmt d "_" c "_" ap "_" f "_" e "_" iso "_" ; }'`

 echo mv $s "$x${y}.jpg"
 mv $s "$x${y}.jpg"
 y=$((y+1))
 echo =====
done
0

You must log in to answer this question.

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