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
- Implementazione
- Riferimenti
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() {
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
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
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"
}
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 }
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
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: 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/
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
- SetTimestampByEximOrFilename
- Linux su Wikipedia e su kernel.org
- Windows su Wikipedia e su sito ufficiale
- Android su Wikipedia e su sito ufficiale
- adb
- exif ed exiftool (Wikipedia e su sito ufficiale)
- WhatsApp e Google Drive
- Shell Bash e PowerShell (vedi anche le nostre Faq Bash)
- Politiche di esecuzione PowerShell
- GitHub
- Ubuntu, RedHat (RHEL), AlmaLinux, Rocky Linux, CentOS