Articolo Fotografia

Sistemare la data di creazione di foto e video

di Enio Carboni 20 Gennaio 2023 - System administrator

Potrebbe capitare di visualizzare foto e video sul gestore dei file o tramite una galleria e rendersi conto che non sono ordinate per data di creazione (la data in cui sono state scattate) perché i file sono stati copiati da un disco all’altro o dopo un ripristino da un vecchio backup e quindi bisognerebbe sistemare la data di creazione.


Mi è capitato da poco, cambiando cellulare e ripristinando l’archivio di WhatsApp da un backup su Google Drive, che la data di modifica sia delle immagini che dei video fossero tutte alla stessa data del giorno di ripristino e che quindi in Galleria si vedessero senza un ordinamento temporaneo.


Mi sono deciso quindi di risolvere il problema senza utilizzare App di terze parti che spesso sono anche a pagamento (o permettono sono la sistemazione di pochi file alla volta).

Sommario

Ipotesi iniziali

Ipotesi iniziali : nome del file

Se il nome del file rispetta un formato dove è presente anche la data e l’ora di creazione allora possiamo utilizzare questa data.
Ad esempio sotto i dispositivi Android le foto hanno un nome del tipo IMG_AAAAMMGG_hhmmss.jpg ed i video del tipo VID_AAAAMMGG_hhmmss.mp4.
Mentre le foto di WhatsApp sono del tipo IMG-AAAAMMDD_WASEQ.jpg ed i video del tipo VID-AAAAMMGG_WASEQ.mp4.

In generale il nome del file può avere i seguenti caratteri:

Dove:

Quindi bisogna semplicemente estrarre la data dal nome ed salvarla nei suoi metadati tramite il comando:

Ipotesi iniziali: Exif

Supponiamo di poter estrarre le informazioni exif dai metadati delle immagini e dei video allora per la data di creazione utilizzeremo:

Per estrarre i metadati exif utilizziamo il comando exiftool sotto Linux mentre in PowerShell sotto Windows utilizzeremo le proprietà di New-Object -ComObject Wia.ImageFile.

Purtroppo sotto la shell ADB di Android non si riesce a risalire facilmente ai metadati quindi salteremo questa ipotesi per Android.

Implementazione

Sistemare la data di creazione sotto Linux o sistemi equivalenti

Sistemare la data di creazione: nome del file

Scriviamo una funzione in Bash, SetTimestampByFilename(), che accetti come parametro un file dove il suo nome rispetti le ipotesi iniziali e supponiamo di avere delle immagini da sistemare la data.

SetTimestampByFilename before
SetTimestampByFilename(): immagini con data iniziale
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\}\).*$/\2./' -e 's/WA/00/' )
  if [ "x$f" != "x$filedate" -a ${#filedate} -eq 15 ]; then
    readable_date=$(echo $filedate | sed -e 's/^\(....\)\(..\)\(..\)\(..\)\(..\)\.\(..\)/\1\/\2\/\3 ::/')
    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
}

Una volta scritta questa funzione (o fatto il copia incolla da qui) basta lanciarla con un percorso di un file come ad esempio:

SetTimestampByFilename $HOME/images/IMG_20230104_154035_e.jpg

oppure su una directory intera:

for f in $HOME/images/*
 do
   SetTimestampByFilename "$f"
 done
SetTimestampByFilenameafter
Esempio SetTimestampByFilename()

Sistemare la data di creazione: Exif

Consideriamo che il comando exiftool sia già installato:

Con exiftool estraiamo il singolo metadato con il parametro -p: ad esempio per estrarre CreateDate nel file myphoto.jpg usiamo:

exiftool -p '$CreateDate' myphoto.jpg

che restituisce il formato: AAAA:MM:DD hh:mm:ss (es.2023:01:04 16:04:18) e lo dovremmo trasformare nel formato per il comando touch -t (AAAAMMDDhhmm.ss) con:

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

Quindi scriviamo una funzione in Bash, SetTimestampByExit(), che accetti come parametro un file:

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/^\(....\)\(..\)\(..\)\(..\)\(..\)\.\(..\)/\1\/\2\/\3 ::/')
    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
}

Una volta scritta questa funzione basta lanciarla con un percorso di un file come ad esempio:

SetTimestampByExit $HOME/images/IMG_20230104_154035_e.jpg

oppure su una directory intera:

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

Sistemare la data di creazione in automatico prima con Exif altrimenti tramite nome del file

Per automatizzare il processo ho creato il repository SetTimestampByEximOrFilename su Github, scaricarlo e scomprimerlo in una cartella.

Copiamo quindi SetTimestampByExifOrFilename.sh in una directory presente nel nostro $PATH ad esempio in $HOME/bin

  # Per sicurezza creiamo, se già non esiste la cartella $HOME/bin
  mkdir -p $HOME/bin
  cp SetTimestampByExifOrFilename.sh $HOME/bin
  chmod +x $HOME/bin/SetTimestampByExifOrFilename.sh
  # Aggiungiamo $HOME/bin alla variabile $PATH se non esiste
  if ! $(echo $PATH | tr ":" "\n" | grep -qx $HOME/bin
    then PATH=$PATH:$HOME/bin
  fi

A questo punto possiamo sistemare le date delle foto o video semplicemente con:

  # Su singolo file
  SetTimestampByExifOrFilename.sh $HOME/images/IMG_20180816_215747.jpg
  # Su tutti i file in una directory
  SetTimestampByExifOrFilename.sh $HOME/images

Lo script proverà prima con i metadati exif e se non li trova con la data nel nome del file.

Sistemare la data di creazione sotto Windows con PowerShell

Come prima cosa dobbiamo aprire un terminale PowerShell ad esempio:

Sistemare la data di creazione: nome del file

Scriviamo una funzione in PowerShell, Set-TimestampByFilename(), che accetti un parametro -File dove il suo nome rispetti le ipotesi iniziali per poterlo utilizzare su immagini con data da sistemare:

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"
}
Set-TimestampByFilename powerShell before
Set-TimestampByFilename(): stato iniziale con data da sistemare

Una volta scritta questa funzione (o fatto il copia incolla da qui) basta lanciarla con un percorso di un file come ad esempio:

Set-TimestampByFilename -File images\IMG_20180816_215747.jpg

oppure su una directory intera:

Get-ChildItem "images" | ForEach-Object { $fix=Set-TimestampByFilename -File $_.Fullname }
Set-TimestampByFilename powerShell after
Set-TimestampByFilename(): esampio di utilizzo

Sistemare la data di creazione: Exif

Scriviamo una funzione PowerShell, Set-TimestampByExif(), che accetti come parametro -File per il passaggio di un file immagine:

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"
}

Una volta scritta (o fatto il copia ed incolla da qui) la funzione basta lanciarla con un persorso di un file come ad esempio:

Set-TimestampByExif -File images\IMG_20230104_154035.jpg

oppure su una directory intera:

  Get-ChildItems "images" | ForEach-Object { $fix=Set-TimestampByExif -File $_.Fullname 
Set-TimestampByExif PowerShell
Set-TimestampByExif(): esempio di utilizzo

Sistemare la data di creazione in automatico prima con Exif altrimenti tramite nome del file

Per automatizzare il processo ho creato il repository SetTimestampByEximOrFilename su Github, scarichiamolo e, per comodità, copiamo Set-TimestampByExifOrFilename.ps1 in una directory appropriata:

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

Nota: bisogna aver il diritto per eseguire gli script *.ps1* quindi verifichiamo con:

  Get-ExecutionPolicy

Se risulta AllSigned o Restricted non è possibile eseguire lo script quindi serve una politica di esecuzione più forte (RemoteSigned, Bypass o UnRestricted) e per sicurezza utilizziamo RemoteSigned sbloccado poi lo script :

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

Possiamo quindi utilizzare lo script su una foto o su tutta una directory.

  cd $ENV:LOCALAPPDATA\SetTimestampByEximOrFilename
  # su un singolo file
  .\Set-TimestampByExifOrFilename.ps1 -File Z:\images\IMG_20230104_154035.jpg
  # Su tutti i file di una directory
  .\Set-TimestampByExifOrFilename.ps1 -File Z:\images

Lo script proverà prima con i metadati exif e se non li trova con la data nel nome del file.

Sistemare la data di creazione sotto Android con shell ADB

Per utilizzare questo script su un dispositivo Android devi connetterlo ad un computer tramite il cavo USB im modo che possa esser utilizzato il comando adb.

Per usare il comando adb e quindi per potersi connettere devi abilitare le Opzioni per gli sviluppatori e poi usb debugging.

Se non sai come farlo, fai una ricerca veloce online come attivare “Debug USB”.

adb può esser scaricato direttamente da dal suo sito.

Una volta scaricato basta decomprimere lo zip in una cartella ed entrare dentro con un terminare Bash per Linux, PowerShell per Windows).

Tramite shell adb non abbiamo la possibilità di risalire facilmente ai metadati exif dato che non è disponibile il comando exiftool quindi consideriamo solo il cambio data associato al nome del file.

Asseconda di come abbiamo installato adb da pacchetto Linux o scaricando lo zip possiamo utilizzare il comando senza il percorso completo o relativo:

  # senza percorso
  adb
  # con percorso relativo se dentro alla cartella contenente adb
  ./adb

Da ora in poi utilizziamo, per semplicità, il comando adb senza percorso, quindi se serve mettere il percorso assoluto di adb nel path di ricerca della shell:

Come prima cosa verifichiamo che il comando adb trovi il nostro dispositivo Android con il comando:

adb devices

Sullo schermo del dispositivo Android dovrebbe comparire una richiesta di accettazione di connessione, accettarla per poter proseguire.

Una volta accettata la richiesta non verrà ripresentata nuovamente.

Sistemare la data di creazione: SetTimestampByFilenameAndroid ADB before
adb shell: lista foto con data da sistemare

Sistemare la data di creazione: nome del file

Entriamo nella shell Android con il comando:

  adb shell

Una volta dentro scriviamo (o facciamo il copia ed incolla) la funzione bash 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\}\).*$/\2./' -e 's/WA/00/' )
  if [ "x$f" != "x$filedate" -a ${#filedate} -eq 15 ]; then
    readable_date=$(echo $filedate | sed -e 's/^\(....\)\(..\)\(..\)\(..\)\(..\)\.\(..\)/\1\/\2\/\3 ::/')
    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
}

Supponiamo di aver una memoria esterna collegata con dentro una cartella “images” dove sono le nostre immagini da modificare e che il suo percorso sia “storage/9C33-6BBD“.

Per verificare il percorso della memoria esterna basta visualizzare la cartella storage oppure usare il comando sm:

  ls -al storage
  sm list-volumes public

Per la memoria interna, cioè la prima disponibile nel dispositivo:

  echo ${EXTERNAL_STORAGE:-/sdcard}

Ora possiamo lanciare la nuova funzione SetTimestampByFilename() con un percorso di un file come ad esempio:

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

oppure su una directory intera:

  for file in storage/9C33-6BBD/images/*; do SetTimestampByFilename "$file"; done
  ls -al storage/9C33-6BBD/images/
sistemare la data di creazione: SetTimestampByFilenameAndroid ADB after
SetTimestampByFilename(): esempio di utilizzo

Appena fatto le modifiche usciamo dalla shell adb con il comando exit.

Naturalmente se vogliamo rientrare in adb shell per modificare altre foto o video dobbiamo riscrivere ogni volta la funzione SetTimestampByFilename() oppure seguiamo i passi del prossimo paragrafo per automatizzare questa attività anche per le volte successive.

Sistemare la data di creazione in automatico tramite copia di uno script

Per automatizzare il processo ho creato il repository SetTimestampByEximOrFilename su Github, scarichiamolo e, per comodità, copiamo lo script SetTimestampByFilenameAndroidAdb.sh su dispositivo Android:

Lo copiamo nella prima memoria interna, dove il percorso lo troviamo:

Quindi copiamo SetTimestampByFilenameAndroidAdb.sh

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

Fatto ciò abbiamo nel nostro dispositivo Android lo script che ci permette di sistemare la data di creazione di foto e video di un singolo file o di una directory direttamente.

  adb shell
  s=${EXTERNAL_STORAGE:-/sdcard}
  echo $s
  cd $s
  # singolo file
  sh ./SetTimestampByFilenameAndroidAdb.sh DCIM/Camera/IMG_20230105_123335.jpg
  sh ./SetTimestampByFilenameAndroidAdb.sh DCIM/Camera/VID_20230105_124001.mp4
  # oppure tutta una directory
  sh ./SetTimestampByFilenameAndroidAdb.sh DCIM/Camera

Riferimenti