Reorder pictures taken by two cameras by time with a shell script (bash) under Linux

Pierre FrançoisSenior consultant
Recently, an awarded photographer, Selina De Maeyer, completed a photo shoot of a beautiful event in Antwerp, the Corpus Christi procession around the Saint James church. She took approximately 250 pictures with her two Nikon cameras, but when we went to publish the pictures in chronological order, we discovered that the time was not set consistently for her cameras.

With software, we were able to extract the .jpg files with the date and time once a picture has been shot. This allowed us to see that there were clearly two series of pictures, separated in time. Reordering so many pictures manually was not an option. So we decided to write a little script to correct the date and time on the set that was wrong.

The first item to address was the origination of the camera for each picture.  There is an open source suite called ImageMagick that provides a program called identify. Identify is able to list information for each picture. All the programs we invoke below are run from the command line interface - the shell. If you never heard about the shell, I am sorry to tell you: do not read further, this article will bore you. but if you are familiar, you will know that grep will filter the lines containing a certain pattern from the results of the previous command.

Information returned by identify includes the time and date when the picture was shot, and also the model of the camera. From this information, we understood that we were on our way to solve the problem.

Running the command
$ identify -verbose *.jpg | grep exif:Model | sort | uniq

Open in new window

On the directory where the pictures were, redirecting the output to the sort and uniq filters gave us the list of cameras used by the photographer.  As you may know, the '$' sign at the beginning of the line is the prompt of the shell, not a part of the command.

This was the output of the command above
    exif:Model: NIKON D3X
                          exif:Model: NIKON D810

Open in new window

The same identify program extracts the date and time at which the picture has been taken in this way:

$ identify -verbose 141.jpg | grep DateTimeOriginal

Open in new window

This, together with the previous use of identify, revealed us that the camera NIKON D810 used by the photographer had both the wrong time and date set. i.e. 2014:04:21 instead of 2015:06:07.

We made the decision to rename every picture of both series to procession-date-time-camera.jpg in such a format that any way of sorting the pictures bases on the name would give the chronological order: year:month:day-hour:minute:second, which is the output format of the corresponding fields in identify. The pictures issued from the D810 camera have the right time, those from the D3X camera will have to undergo a big correction.

But before proceeding further, we will check to see if one of the cameras shot more than one picture in a second. This is easily done be issuing next command:
$ identify -verbose *.jpg | grep DateTimeOriginal | sort | uniq -c 

Open in new window

The effect of running uniq with the -c flag is to add a number in front of each line counting the number of repeated lines. We were surprised to see that this happened 7 times. So, for avoiding deleting files by using the same name twice, we changed the generated file names into the next format: procession-date-time-camera-basename.jpg where basename is that part of the file name that is stripped from its prefix (the path to the file, if present) and suffix (.jpg here).

There is still one problem we didn't solve before starting to code our script. How will we correct the date of the camera with an incorrect time setting? By comparing pictures from both series, we can conclude that the date and time of the D810 camera must be correct by + one year, + two months, - 14 days, + 4 hours, + 20 minutes, and the other camera, D3X, only with + 1 hour.

The knowledge of the date command of the shell helped us to remember that there is an option to compute and format dates in the past and in the future. the date within 40 hours can be output this way:

$ date --date='now + 40 hours'

Open in new window

This arithmetic on dates only works on the GNU versions of date. So BSD and OS X users cannot access this feature in the way I describe here.

We still have to understand how to make it clear for date to use the same format as identify uses. Digging into the manual page of date, we can conclude that the dates must be formatted using the -f flag in this way:

$ date +"%Y:%m:%d %H:%M:%S" -d '2014/04/21 5:42:36 +0200'

Open in new window

The +0200 argument is to be used in Western Europe during the summer. The output of the above command gives:

2014:04:21 05:42:36

Open in new window

Combining this with what we have learned further above, we can correct a date and format it with next command line:
date +"%Y:%m:%d %H:%M:%S" -d '2014/04/21 5:42:36 +0200 + 1 year + 2 months - 14 days + 4 hours + 20 min'

Open in new window

This gives us the result we hoped. See the output below.

2015:06:07 10:02:36

Open in new window

Great! We have all the elements to start coding. We are going to write our results into a new directory called renamed-files. For understanding the code, you need to know the syntax of the bash shell scripts. See the commented code below:

                      rm -f $destdir/*
                      for pict in *.jpg ; 
                      do      basename=$(basename $pict .jpg)
                              camerainfo=$(identify -verbose $pict | grep exif:Model)
                              # strip begin of line from camerainfo
                              camera=${camerainfo/    exif:Model: /}
                              # change spaces into underscores in camera
                              camera=${camera// /_}
                              shotdatetimeinfo=$(identify -verbose $pict | grep DateTimeOriginal)
                              # strip begin of line from shotdatetimeinfo
                              shotdatetime=${shotdatetimeinfo/    exif:DateTimeOriginal: /}
                              # format date as suitable input for date command, separator is "/"
                              #  format time is suitable as input for date command, separator is ":"
                              # correct date and time here, according to camera
                              if [ $camera == "NIKON_D810" ]
                              then    correctdate=$(date +"%Y%m%d" -d "$shotdate $shottime +0200 + 1 year + 2 months - 14 days + 4 hours + 20 min")
                                      correcttime=$(date +"%H%M%S" -d "$shotdate $shottime +0200 + 1 year + 2 months - 14 days + 4 hours + 20 min")
                              else    # $camera == "NIKON_D3X" 
                                      correctdate=$(date +"%Y%m%d" -d "$shotdate $shottime +0200 + 1 hour")
                                      correcttime=$(date +"%H%M%S" -d "$shotdate $shottime +0200 + 1 hour")
                              cp $pict $destdir/procession-$correctdate-$correcttime-$camera-$basename.jpg

Open in new window

I was able to run this code twice - once on the directory of the Low Definition pictures and once on the directory with the High Definition pictures. I am quite pleased this solved my problem in a couple of hours.
Pierre FrançoisSenior consultant

Comments (1)

Pierre FrançoisSenior consultant


Dear Brad,

Thanks for fixing the typos.

You said:
A volunteer Page Editor may engage you on the content to improve your article further.

I would be happy to know what I have to do to get this done.

Thanks in advance.

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.