Sistemare la data di creazione di foto e video

Sistemare la data di creazione di foto e video

See the article in English

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

  • Supponiamo di avere uno o più file foto o video con la data di modifica errata rispetto a quella di creazione dello scatto;
  • stiamo utilizzando un ambiente Linux, Windows o un dispositivo Android;
  • il file abbia i metadati exif oppure la data di creazione sia parte del nome del file.

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:

  • inizi con almeno 3 lettere e non più di 5 minuscole o maiuscole ( ^[A-Za-z]\{3,5\} );
  • seguito dal carattere “” o “_” ( [_-] );
  • seguito da 8 cifre ( AAAAMMDD [0-9]\{8\} );
  • seguito dal carattere “” o “_” ( [_-] );
  • seguito da 2 caratteri (2 cifre o WA .\{2\} );
  • seguito da 4 cifre ( mmss [0-9]\{4\} ).
  • seguito da altri caratteri compresa l’estensione del file.

Dove:

  • AAAA è l’anno di 4 cifre dello scatto;
  • MM   è il mese di 2 cifre dello scatto;
  • DD   è il giorno di 2 cifre dello scatto;
  • hh   sono le ore di 2 cifre dello scatto;
  • mm   sono i minuti di 2 cifre dello scatto;
  • ss   sono i secondi di 2 cifre dello scatto;
  • WA   indica che il file è di WhatsApp e quindi si perdono le ore;
  • SEQ  è una sequenza di 4 cifre a partire da 0000 e si incrementa di uno ad ogni nuova immagine dello stesso giorno: si perdono o minuti ed i secondi.

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

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

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 le foto:
    • prima ExifDTOrig (id 36868);
    • poi ExifDTDigitized (id 36867) se non esiste il primo
  • per i video:
    • MediaCreateDate

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\}\).*$/./' -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
}

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:

  • 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

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

  • cliccando con il tasto destro del mouse nel munù Start e quindi su Windows PowerShell;
  • tasto WIN + R (finestra esegui) e digitando powershell e quindi il tasti invio;
  • tasto WIN + S (finestra ricerca) e cercando powershell e quindi cliccando su Apri nella destra
  • oppure ogni altro metodo.

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:

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

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\}\).*$/./' -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
}

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:

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

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

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Q Captcha + 15 = 17