Fix the creation date of photos and videos

Fix creation date of photos and videos

Vedi L'articolo in Italiano

It may happen that you view your photos and videos in your file manager or through a gallery and realize that they are not sorted by creation date (the date they were taken) because the files were copied from one disk to another or after a restore from an old backup, so we need to fix creation date.

It happened to me recently, changing mobile and restoring the WhatsApp archive from a backup on Google Drive, that the modification date of both the images and videos were all on the same date as the day of restoration and therefore in the Gallery they were seen without a temporary arrangement.

I therefore decided to solve the problem without using third-party apps which are often paid for (or only allow the arrangement of a few files at a time).

Table of Contents

Initial hypothesis

  • Suppose we have one or more photo or video files with incorrect modification data compared to that of the creation of the shot;
  • we are using a Linux, windows or android device;
  • the file has exif metadata or the creation date is part of the file name.

Initial hypothesis: the creation date is part of the file name

If the file name respects a format where the date and time of creation are also present, then we can use this date.
For example, under Android devices, photos have a name like IMG_YYYYMMDD_hhmmss.jpg and videos like VID_YYYYMMDD_hhmmss.mp4.
WhatsApp photos are of the type IMG-YYYYMMDD_WASEQ.jpg and videos of the type VID-YYYYMMDD_WASEQ.mp4.

In general, the file name can have the following characters:

  • start with at least 3 letters and no more than 5 lowercase or uppercase ( ^[A-Za-z]\{3,5\} );
  • followed by the character “” o “_” ( [_-] );
  • so by 8-digit ( AAAAMMDD [0-9]\{8\} );
  • followed by “” o “_” ( [_-] );
  • than 2-digit (2 cifre o WA .\{2\} );
  • followed by 4-digit ( mmss [0-9]\{4\} ).
  • finally followed by other characters including the file extension.

Where:

  • AAAA is the 4-digit year of the shot;;
  • MM is the 2-digit month of the shot;
  • DD is the 2-digit day of the shot;
  • hh is the 2-digit hours of the shot;
  • mm is the 2-digit minutes of the shot;
  • ss is the 2-digit seconds of the shot;
  • WA indicates that the file is from WhatsApp (we don’t have the 2-digits of the hours);
  • SEQ is a sequence of 4 digits starting from 0000 and increases by one with each new image of the same day (minutes and seconds are lost).

For these files, simply extract the date from the name and save it in its metadata using the command:

  • Android or Linux: touch -t "AAAAMMGGhhmm.ss"
  • Windows PowerShell: $fileImg.creationtime=$newdate; $fileImg.lastwritetime=$newdate

Initial hypothesis: file has exif metadata

Suppose we can extract the exif information from the image and video metadata then for the creation date we will use:

  • for the photos
    • first ExifDTOrig (id 36868);
    • than ExifDTDigitized (id 36867) if the first doesn’t exist
  • for videos:
    • MediaCreateDate

To extract the exif metadata we use the exiftool command under Linux while in PowerShell under Windows we will use the properties of New-Object -ComObject Wia.ImageFile.

Unfortunately under the ADB shell of Android it is not possible to easily trace the metadata so we will skip this hypothesis for Android.

Implementation

Fix the creation date under Linux or equivalent systems

Fix the creation date: file name

Let’s write a function in Bash SetTimestampByFilename(), which accepts as a parameter a file where its name respect the initial hypotheses and suppose we have some images to fix the date:

Fix creation date: SetTimestampByFilename before
SetTimestampByFilename(): images with incorrect start date
SetTimestampByFilename() {
  local f=$(basename "$1")
  local filedate=""
  filedate=$( echo $f | sed -e 's/^\([A-Za-z]\{3,5\}\)[_-]\([0-9]\{8\}\)[_-]\(.\{2\}\)\([0-9]\{2\}\)\([0-9]\{2\}\).*$/./' -e 's/WA/00/' )
  if [ "x$f" != "x$filedate" -a ${#filedate} -eq 15 ]; then
    readable_date=$(echo $filedate | sed -e 's/^\(....\)\(..\)\(..\)\(..\)\(..\)\.\(..\)/\/\/ ::/')
    echo "$1: setting date to $readable_date ($filedate)"
    touch -t "$filedate" "$1"
    return 0
   else
     echo "ERR: Invalid file name format: $1" >&2
     return 1
  fi
}

Once this function has been written (or done the copy paste from here) just launch it with a path to a file such as:

SetTimestampByFilename $HOME/images/IMG_20230104_154035_e.jpg

or on an entire directory:

for f in $HOME/images/*
 do
   SetTimestampByFilename "$f"
 done
Fix creation date: SetTimestampByFilename after
SetTimestampByFilename() example

Fix the creation date: Exif

We assume that the exiftool command is already installed:

  • Ubuntu and derivatives: apt install -y libimage-exiftool-perl
  • RHEL 9:
    • subscription-manager repos --enable codeready-builder-for-rhel-9-$(arch)-rpms
    • dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm
  • AlmaLinux 9, Rocky Linux 9:
    • dnf config-manager --set-enabled crb
    • dnf install epel-release
  • RHEL 8:
    • subscription-manager repos --enable codeready-builder-for-rhel-8-$(arch)-rpms
    • dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
  • AlmaLinux 8, Rocky Linux 8:
    • dnf config-manager --set-enabled powertools
    • dnf install epel-release
  • RHEL 7:
    • subscription-manager repos --enable rhel-\*-optional-rpms --enable rhel-\*-extras-rpms --enable rhel-ha-for-rhel-\*-server-rpms
    • yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
  • CentOS 7:
    • yum install epel-release

With exiftool we extract the single metadata with the -p parameter: for example to extract CreateDate in the file myphoto.jpg we use:

exiftool -p '$CreateDate' myphoto.jpg

which returns the format: AAAA:MM:DD hh:mm:ss (ex.2023:01:04 16:04:18) and we should transform it to the format for the touch -t command (AAAAMMDDhhmm.ss)
:

exiftool -p '$CreateDate' myphoto.jpg 2>/dev/null | sed -e 's/[: ]//g' -e 's/\(..$\)/\./')

So let’s write a function in Bash, SetTimestampByExit(), which takes a file as a parameter:

SetTimestampByExif() {
  local filedate=""
  local readable_date=""
  filedate=$(exiftool -p '$MediaCreateDate' "$1" 2>/dev/null | sed -e 's/[: ]//g' -e 's/\(..$\)/\./')
  if [ -z "$filedate" ]; then
    filedate=$(exiftool -p '$CreateDate' "$1" 2>/dev/null | sed -e 's/[: ]//g' -e 's/\(..$\)/\./')
    if [ -z "$filedate" ]; then
      filedate=$(exiftool -p '$DateTimeOriginal' "$1" 2>/dev/null | sed -e 's/[: ]//g' -e 's/\(..$\)/\./')
    fi
  fi
  if [ -n "$filedate" ]; then
    readable_date=$(echo $filedate | sed -e 's/^\(....\)\(..\)\(..\)\(..\)\(..\)\.\(..\)/\/\/ ::/')
    date --date "$readable_date" >/dev/null 2>&1
    if [ $? -ne 0 ]; then
      echo "ERR: $1: invalid exif date format" >&2
      return 2
    fi
    echo "$1: setting date exif to $readable_date ($filedate)"
    touch -t "$filedate" "$1"
    return 0
  else
    echo "ERR: exif date not found: $1" >&2
    return 1
  fi
}

Once you write this function just launch it with a path to a file such as:

SetTimestampByExit $HOME/images/IMG_20230104_154035_e.jpg

or on an entire directory:

for f in $HOME/images/*
 do
   SetTimestampByExit "$f"
 done
Fix creation date: SetTimestampByExif
SetTimestampByExif(): esempio di utilizzo

Fix creation date automatically first with Exif otherwise by file name

To automate the process I created the repository SetTimestampByEximOrFilename su Github, download it and unzip it in a folder.

So let’s copy SetTimestampByExifOrFilename.sh in a directory present in our $PATH for example in $HOME/bin:

  # First create, if it doesn't already exist, the $HOME/bin folder
  mkdir -p $HOME/bin
  cp SetTimestampByExifOrFilename.sh $HOME/bin
  chmod +x $HOME/bin/SetTimestampByExifOrFilename.sh
  # Add $HOME/bin to the $PATH variable if it doesn't exist
  if ! $(echo $PATH | tr ":" "\n" | grep -qx $HOME/bin
    then PATH=$PATH:$HOME/bin
  fi

At this point we can fix the dates of the photos or videos simply with:

  # On single file
  SetTimestampByExifOrFilename.sh $HOME/images/IMG_20180816_215747.jpg
  # On all file in a directory
  SetTimestampByExifOrFilename.sh $HOME/images

The script will try with the exif metadata first and if it doesn’t find it with the date in the filename.

Fix the creation date under Windows with PowerShell

First we need to open a PowerShell terminal for example:

  • clicking with the right mouse button in the Start menu and then on Windows PowerShell;
  • or WIN + R key (run window) and typing powershell and then the enter keys;
  • otherwise WIN + S key (search window) and looking for powershell and then clicking on Open in the right;
  • or any other method.

Fix the creation date: file name

Let’s write a function in PowerShell, Set-TimestampByFilename(), which accepts a -File parameter where its name respect the initial hypotheses and suppose we have some images to fix the date:

function Set-TimestampByFilename() {
  [CmdletBinding()]
  param (
    [Parameter(Mandatory)] [string]$File
  )
  if (! (Test-Path $File)) {
    return "ERR: ${File}: not exist"
  }
  $fileName=$(Get-Item $File).Name
  if (! ($fileName -match '^[A-Za-z]{3,5}[_-](\d{4})(\d{2})(\d{2})[_-](WA|\d{2})(\d{2})(\d{2}).*$')) {
    return "ERR: Invalid file name format: ${fileName}"
  }
  $filedate=$Matches[1] + '/' + $Matches[2] +'/'+ $Matches[3] +' '+ $Matches[4] +':'+ $Matches[5] +':'+ $Matches[6]
  $filedate = $filedate -replace 'WA','00'
  $(Get-Item $File).creationtime=$(Get-Date $filedate)
  $(Get-Item $File).lastwritetime=$(Get-Date $filedate)
  Write-Host "${File}: setting date to ${filedate}"
  return "OK"
}
Fix creation date: Set-TimestampByFilename_powerShell before
Set-TimestampByFilename(): images with incorrect start dates

Once this function has been written (or done the copy paste from here) just launch it with a path to a file such as:

Set-TimestampByFilename -File images\IMG_20180816_215747.jpg

or on an entire directory:

Get-ChildItem "images" | ForEach-Object { $fix=Set-TimestampByFilename -File $_.Fullname }
Fix creation date: Set-TimestampByFilename_powerShell after
Set-TimestampByFilename(): example

Fix the creation date: Exif

Let’s write a PowerShell function, Set-TimestampByExif(), which accepts -File as a parameter for passing an image file:

function Set-TimestampByExif() {
  [CmdletBinding()]
  param (
    [Parameter(Mandatory)] [string]$File
  )
  if (! (Test-Path $File)) {
    return "ERR: ${File}: not exist"
  }
  $fullName=$(Get-Item $File).FullName
  $image = New-Object -ComObject Wia.ImageFile
  try {
    $image.LoadFile($fullName);
  }
  catch {
    return "ERR: ${File}: not an image file"
  }
  if ($image.Properties.Length -eq 0) {
    return "ERR: ${File}: not an image file"
  }
  if ( $image.Properties.Exists('ExifDTOrig')) {
    $filedate=$image.Properties.Item('ExifDTOrig').Value
  }
  elseif ($image.Properties.Exists('ExifDTDigitized')) {
    $filedate=$image.Properties.Exists('ExifDTDigitized').Value
  }
  else {
    return "ERR: ${File}: Exif date not found"
  }
  if ($filedate -match '^\d\d\d\d:\d\d:\d\d \d\d:\d\d:\d\d$') {
    $filedate_obj=[datetime]::ParseExact($filedate,'yyyy:MM:dd HH:mm:ss',$null)
  }
  else {
    return "ERR: ${File}: Exif date unrecognized format ($filedate)"
  }
  $(Get-Item $File).creationtime=$filedate_obj
  $(Get-Item $File).lastwritetime=$filedate_obj
  Write-Host "${File}: setting exif date to ${filedate}"
  return "OK"
}

Once you write this function just launch it with a path to a file such as:

Set-TimestampByExif -File images\IMG_20230104_154035.jpg

or on an entire directory:

  Get-ChildItems "images" | ForEach-Object { $fix=Set-TimestampByExif -File $_.Fullname 
Fix creation date: Set-TimestampByExif_powerShell
Set-TimestampByExif(): example

Fix creation date automatically first with Exif otherwise by file name

To automate the process I created the repository SetTimestampByEximOrFilename su Github, download it and unzip it in a folder than copy Set-TimestampByExifOrFilename.ps1 in a directory.

  mkdir -Force $ENV:LOCALAPPDATA\SetTimestampByEximOrFilename
  cp Set-TimestampByExifOrFilename.ps1 $ENV:LOCALAPPDATA\SetTimestampByEximOrFilename

Note: You need to have the right to run .ps1 scripts so let’s check with:

  Get-ExecutionPolicy

if it’s AllSigned or Restricted it is not possible to run the script so you need a stronger execution policy (RemoteSigned, Bypass or UnRestricted)and for security we use RemoteSigned and then unlock the script :

  Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -Force
  Unblock-File -Path $ENV:LOCALAPPDATA\SetTimestampByEximOrFilename\Set-TimestampByExifOrFilename.ps1

We can then use the script on a photo or on an entire directory.

  cd $ENV:LOCALAPPDATA\SetTimestampByEximOrFilename
  # single file
  .\Set-TimestampByExifOrFilename.ps1 -File Z:\images\IMG_20230104_154035.jpg
  # All file on a directory
  .\Set-TimestampByExifOrFilename.ps1 -File Z:\images

The script will try with the exif metadata first and if it doesn’t find it with the date in the filename.

Fix the creation date under Android with ADB

To use this script on an Android device you need to connect it to a computer via USB cable so that the adb command can be used.

To use the adb command and therefore to be able to connect you need to enable the Developer options and then usb debugging.

If you don’t know how to do it, do a quick search online how to enable “USB Debugging”

adb can be dowload directly from its site.

Once downloaded just unzip the zip in a folder and enter inside with a terminate (Bash for Linux, PowerShell for Windows)

Through the adb shell we don’t have the possibility to easily trace the exif metadata since the exiftool command is not available so we only consider the date change associated with the file name.

Second as we installed adb from Linux package or by downloading the zip we can use the command without the full or relative path:

  # without peth
  adb
  # with relative path if inside the folder containing adb
  ./adb

From now on we use, for simplicity, the command adb without path, so if you need to put the absolute path of adb in the shell search path:

  • on Bash: PATH:=$PATH:$(pwd)
  • on PowerShell: $ENV:PATH += ';' + $(pwd)

First, let’s verify that the adb command finds our Android device with the command:

adb devices

A connection acceptance request should appear on the screen of the Android device, accept it in order to continue.

Once accepted, the request will not be resubmitted again.

Fix creation date: SetTimestampByFilenameAndroid_ADB before
adb shell: photo list with date to fix

Fix the creation date: file name

We enter the Android shell with the command:

  adb shell

Once inside, write (or copy and paste) the bash function SetTimestampByFilename()

SetTimestampByFilename() {
  local f=$(basename "$1")
  local filedate=""
  filedate=$( echo $f | sed -e 's/^\([A-Za-z]\{3,5\}\)[_-]\([0-9]\{8\}\)[_-]\(.\{2\}\)\([0-9]\{2\}\)\([0-9]\{2\}\).*$/./' -e 's/WA/00/' )
  if [ "x$f" != "x$filedate" -a ${#filedate} -eq 15 ]; then
    readable_date=$(echo $filedate | sed -e 's/^\(....\)\(..\)\(..\)\(..\)\(..\)\.\(..\)/\/\/ ::/')
    echo "$1: setting date to $readable_date ($filedate)"
    touch -t "$filedate" "$1"
    return 0
   else
     echo "Invalid file name format: $1" >&2
     return 1
  fi
}

Suppose we have an external memory connected with an “images” folder inside where our images to be modified are and that its path is “storage/9C33-6BBD“.

To verify the path of the external memory just view the storage folder or use the sm command:

  ls -al storage
  sm list-volumes public

For the internal memory, i.e. the first available in the device:

  echo ${EXTERNAL_STORAGE:-/sdcard}

Now we can launch the new function SetTimestampByFilename() with a path to a file such as:

  SetTimestampByFilename storage/9C33-6BBD/images/IMG_20191006_100750_BURST3.jpg
  ls -al storage/9C33-6BBD/images/IMG_20191006_100750_BURST3.jpg

or on an entire directory:

  for file in storage/9C33-6BBD/images/*; do SetTimestampByFilename "$file"; done
  ls -al storage/9C33-6BBD/images/
Fix creation date: SetTimestampByFilenameAndroid_ADB after
SetTimestampByFilename(): example

As soon as the changes have been made, exit the adb shell with the exit command:

Naturally, if we want to re-enter adb shell to fix other photos or videos we have to rewrite the SetTimestampByFilename() function each time or we can follow the steps in the next paragraph to automate this activity also for subsequent times.

Fix creation date automatically by file name

To automate the process I created the repository SetTimestampByEximOrFilename su Github, download it and unzip it in a folder than copy SetTimestampByFilenameAdb.sh in a directory.

We copy it to the first internal memory, and we find the path with:

  • on Bash:
    pri_storage=$(adb shell 'echo $EXTERNAL_STORAGE')
    pri_storage=${pri_storage:-/sdcard}
    echo $pri_storage
  • on PowerShell:
    $pri_storage=$(adb shell 'echo $EXTERNAL_STORAGE')
    if (-not $pri_storage) {$pri_storage='/sdcard'}
    echo $pri_storage

So copy SetTimestampByFilenameAndroidAdb.sh

  adb push SetTimestampByFilenameAndroidAdb.sh $pri_storage
  adb shell ls -al $pri_storage/

having done this we have in our Android device the script that allows us to fix the date of creation of photos and videos of a single file or directory.

  adb shell
  s=${EXTERNAL_STORAGE:-/sdcard}
  echo $s
  cd $s
  # single file
  sh ./SetTimestampByFilenameAndroidAdb.sh DCIM/Camera/IMG_20230105_123335.jpg
  sh ./SetTimestampByFilenameAndroidAdb.sh DCIM/Camera/VID_20230105_124001.mp4
  # or all file in a directory
  sh ./SetTimestampByFilenameAndroidAdb.sh DCIM/Camera

References

Leave a Reply

Your email address will not be published. Required fields are marked *

48 − = 39