|
#!/usr/bin/env bash |
|
|
|
|
|
cmd=$(basename "${0}"); |
|
now=$(date "+%Y%m%d%H%M%S"); |
|
|
|
# Define file extensions to look for: |
|
extensions="jpeg jpg png gif mp4 avi" |
|
|
|
|
|
|
|
# function helpMessage()#{{{ |
|
function helpMessage() { |
|
echo "" |
|
echo "${cmd} - Fix file timestamps." |
|
echo "" |
|
echo "USAGE"; |
|
echo " ${cmd} [--silent] <path> [days]"; |
|
echo " ${cmd} -h"; |
|
echo "" |
|
echo "DESCRIPTION" |
|
echo " Fix media files ctime and mtime acording " |
|
echo " exif headers or filename pattern." |
|
echo "" |
|
echo "ARGUMENTS" |
|
echo " --silent Supress feedback output." |
|
echo " <path> Path from which to search." |
|
echo " [days] Maximum days ago of current file timestamp" |
|
echo " to be re-checked (default: no limit)." |
|
echo "" |
|
} |
|
#}}} |
|
|
|
# function writetxt()#{{{ |
|
function writetxt() { |
|
if [[ "${silentMode}" == "1" ]]; then return; fi; |
|
echo $@ |
|
} |
|
#}}} |
|
|
|
# function smartident()#{{{ |
|
function smartident() { |
|
# Do almost nothing but avoid time waste ;-) |
|
|
|
local fpath="${1}"; |
|
|
|
if [[ "${fpath}" =~ \.mp4$ ]]; then |
|
|
|
# Workarround issue with .mp4 files that identify seem to threat as |
|
# (too many) image files and delay too much to finish. Also they seem |
|
# to be temp files so their 'date:create:' 'date:modify:' headers become |
|
# current time resulting in wrong result. |
|
|
|
# FIX 1: Process in background and capture only first 1000 rows. |
|
## Credits: https://stackoverflow.com/a/20018118/4243912 |
|
## DISABLED because the only timestamp source left was a 'Base |
|
## filename' field in case it contains timestamp string. |
|
## ...but later I checked that this is also generated by identify |
|
## using current file name (so even not overcomes renamings). |
|
##exec 3< <(identify -verbose "${fpath}") |
|
##head -n 1000 <&3 |
|
|
|
|
|
# FIX 2: Just do nothing. |
|
# Omitting identify output we save time and processing too |
|
# and even hope in file name timestamp patterns if we are lucky. |
|
|
|
|
|
# TODO: Find better way to gues mp4 header timestamps (if they really |
|
# exists). |
|
|
|
echo ""; # Fix bash 'if' stamment crying for nothint to do ;-) |
|
|
|
else |
|
|
|
identify -verbose "${fpath}"; |
|
|
|
fi; |
|
} |
|
#}}} |
|
|
|
# function commonVars()#{{{ |
|
function commonVars() { |
|
|
|
# Timestamp capturing regex: |
|
regex_timestamp=' |
|
^ # String begin |
|
(?: |
|
.*?:\s* # Some label ended in ":" and optional spacing |
|
|.*?\D # ...or anything up to some non numeric character |
|
)? # ...or just nothing (beginnig of the string) |
|
|
|
# Capture (YYYY)(MM)(DD) with optional "-" or ":" separators: |
|
([12]\d{3})[:-]?(\d{2})[:-]?(\d{2}) |
|
|
|
# Capture additional (HH)(MM)[SS] (if given) |
|
(?:[:_\s-]?(\d{2})[:-]?(\d{2})[:-]?(\d{2})?)? |
|
|
|
# Followed by non-digit character or simply the end of the string: |
|
(?:(\D.*))?$ |
|
|
|
# Capture whole string (to remove it) else case: |
|
|.* |
|
'; |
|
|
|
|
|
pl_capture_date="s/${regex_timestamp}/\$1\$2\$3/x"; |
|
pl_capture_time="s/${regex_timestamp}/\$4\$5\$6/x"; |
|
pl_capture_timestamp="s/${regex_timestamp}/\$1\$2\$3\$4\$5\$6/x"; |
|
|
|
} |
|
#}}} |
|
|
|
# function guessdate()#{{{ |
|
function guessdate() { |
|
## REQUIRES commonVars ## |
|
|
|
local fpath="${1}"; |
|
local fname=$(basename "${fpath}"); |
|
local ident=$(smartident "${fpath}"); |
|
|
|
# Calculate earliest reasonable time limit: |
|
local filetimestamp=$( \ |
|
( \ |
|
echo "${now}"; |
|
stat --printf '%x\n%y' "${fpath}" \ |
|
) | sort -u \ |
|
| head -n 1 \ |
|
| perl -pe "${pl_capture_timestamp}" \ |
|
); |
|
|
|
# Search time patterns against exif and non exif headers and filename: |
|
local timestr=$( \ |
|
( \ |
|
echo "$ident" | grep -i 'exif:DateTimeOriginal:' \ |
|
|| echo "$ident" | grep -i 'exif:DateTimeDigitalized:' \ |
|
|| echo "$ident" | grep -i 'exif:DateTime:' \ |
|
|| echo "$ident" | grep -i 'date:create:' \ |
|
|| echo "$ident" | grep -i 'date:modify:' \ |
|
|| echo "$ident" | grep -i 'exif:GPSDateStamp:' \ |
|
|| echo "$fname" \ |
|
) \ |
|
| head -n 1 \ |
|
); |
|
|
|
local datepart=$(echo "${timestr}" | perl -pe "${pl_capture_date}"); |
|
local timepart=$(echo "${timestr}" | perl -pe "${pl_capture_time}"); |
|
[[ -n "${timepart}" ]] || timepart="0000"; |
|
|
|
local t="${datepart}${timepart}"; |
|
local timestring="${t:0:4}-${t:4:2}-${t:6:2} ${t:8:2}:${t:10:2}"; |
|
[[ -n "${t:13:1}" ]] && timestring="${timestring}:${t:12:2}"; # Consider seconds if available. |
|
|
|
# Check $t is valid and not after $filetimestamp. |
|
[[ -n "${t}" ]] \ |
|
&& [[ "${t}" < "${filetimestamp}" ]] \ |
|
&& date -d "${timestring}" > /dev/null 2>&1 |
|
local time_err=$? |
|
|
|
|
|
if [[ "${time_err}" -eq "0" ]]; then |
|
# Got valid timestamp earlier than file time and not in the future. |
|
###echo " * $fpath ---> $t"; |
|
|
|
local ttime="${t:0:12}"; |
|
[[ -n "${t:13:1}" ]] && ttime="${ttime}.${t:12:2}"; # Consider seconds in touch format |
|
|
|
touch -c -t "${ttime}" "${fpath}" |
|
writetxt -n '!' |
|
else |
|
writetxt -n '.' |
|
fi; |
|
|
|
} |
|
#}}} |
|
|
|
### ( Arguments processing ) ### |
|
|
|
# Modifiers: |
|
if [[ "${1}" == "-h" ]]; then |
|
helpMessage; |
|
exit 0; |
|
fi; |
|
|
|
silentMode="0"; |
|
if [[ "${1}" == "--silent" ]]; then |
|
silentMode="1"; |
|
shift; |
|
fi; |
|
|
|
# Starting path |
|
if ! [[ "${1}" =~ ^[0-9]*$ ]]; then |
|
pathArg=$(realpath "${1}") |
|
shift; |
|
else |
|
echo 'Path argument is mandatory!!' |
|
echo ' Use "./" if you want to search from current path.' |
|
echo ' Type "${cmd} -h" for help message.' |
|
exit 1; |
|
fi; |
|
|
|
# Optional days to check (default infinite) argument |
|
if [[ "${1}" =~ ^[0-9]+$ ]]; then |
|
daysArg=" -a -mtime -${1}" |
|
else |
|
daysArg="" |
|
fi; |
|
|
|
|
|
### ( Main ) ### |
|
|
|
patts="-iname '*.$(echo $extensions | perl -pe "s/ /' -o -iname '*./g")'" |
|
listfilesCmd="find \"${pathArg}\" \( ${patts} \)${daysArg}" |
|
|
|
writetxt -n "Processing:"; |
|
commonVars; |
|
eval "${listfilesCmd}" | while read f; do |
|
guessdate "${f}"; |
|
done; |
|
writetxt ''; |
|
writetxt 'DONE!!'; |