|
#!/bin/bash |
|
|
|
# Check if enough arguments are provided |
|
main() { |
|
check_dependencies |
|
parse_arguments "$@" |
|
init_log |
|
check_folders |
|
initialize_statistics |
|
start_processing |
|
display_final_statistics |
|
log_final_statistics |
|
show_exit_prompt |
|
} |
|
|
|
check_dependencies() { |
|
local missing_deps=() |
|
|
|
# Check for exiftool |
|
if ! command -v exiftool &> /dev/null; then |
|
missing_deps+=("exiftool") |
|
fi |
|
|
|
# Check for jq |
|
if ! command -v jq &> /dev/null; then |
|
missing_deps+=("jq") |
|
fi |
|
|
|
# If any dependencies are missing, show error and exit |
|
if [ ${#missing_deps[@]} -gt 0 ]; then |
|
echo -e "\e[31m❌ Missing required dependencies:\e[0m" |
|
for dep in "${missing_deps[@]}"; do |
|
echo -e " \e[33m• $dep\e[0m" |
|
done |
|
echo "" |
|
echo -e "\e[32m📦 Installation instructions:\e[0m" |
|
echo "" |
|
echo -e "\e[37mUbuntu/Debian:\e[0m" |
|
echo -e " sudo apt update && sudo apt install exiftool jq" |
|
echo "" |
|
echo -e "\e[37mCentOS/RHEL/Fedora:\e[0m" |
|
echo -e " sudo yum install perl-Image-ExifTool jq" |
|
echo "" |
|
echo -e "\e[37mmacOS (with Homebrew):\e[0m" |
|
echo -e " brew install exiftool jq" |
|
echo "" |
|
echo -e "\e[37mArch Linux:\e[0m" |
|
echo -e " sudo pacman -S perl-image-exiftool jq" |
|
echo "" |
|
echo -e "\e[37mOpenSUSE:\e[0m" |
|
echo -e " sudo zypper install perl-Image-ExifTool jq" |
|
echo "" |
|
echo -e "\e[33m💡 After installing, run the script again.\e[0m" |
|
exit 1 |
|
fi |
|
|
|
echo -e "\e[32m✅ All dependencies are installed\e[0m" |
|
} |
|
|
|
parse_arguments() { |
|
exclude_patterns=() |
|
|
|
# Check for help flag first |
|
for arg in "$@"; do |
|
if [[ "$arg" == "--help" || "$arg" == "-h" ]]; then |
|
show_help |
|
exit 0 |
|
fi |
|
done |
|
|
|
if [ "$#" -lt 1 ]; then |
|
echo -e "\e[31mUsage: $0 srcfolder [-l logfile.log] [-e exclude_pattern]... \e[0m" |
|
echo -e "\e[33mParameters:\e[0m" |
|
echo -e " srcfolder Source directory containing files to process." |
|
echo -e "\e[33mOptions:\e[0m" |
|
echo -e " -l logfile.log Specify a log file to store processing information." |
|
echo -e " -e exclude_pattern Exclude files or directories that match the pattern (can be used multiple times)." |
|
echo -e "\e[33mVariables:\e[0m" |
|
echo -e " FIND_OPTS Specify additional options for the find command (e.g., size limits, excluding specific directories) over the command line variable FIND_OPTS." |
|
echo -e "\e[33mHelp:\e[0m" |
|
echo -e " --help, -h Show this help message." |
|
exit 1 |
|
fi |
|
|
|
srcfolder=$1 |
|
logfile="" |
|
|
|
shift 1 |
|
while getopts "l:e:o:h" opt; do |
|
case $opt in |
|
e) |
|
exclude_patterns+=($OPTARG) |
|
;; |
|
l) |
|
logfile=$OPTARG |
|
;; |
|
h) |
|
show_help |
|
exit 0 |
|
;; |
|
*) |
|
echo -e "\e[31mUsage: $0 srcfolder [-l logfile.log]\e[0m" |
|
exit 1 |
|
;; |
|
esac |
|
done |
|
} |
|
|
|
show_help() { |
|
echo -e "\e[33m=========================================\e[0m" |
|
echo -e "\e[32m Photo Timestamp Fixer\e[0m" |
|
echo -e "\e[33m=========================================\e[0m" |
|
echo "" |
|
echo -e "\e[37mThis script processes Google Photos Takeout files to restore original timestamps.\e[0m" |
|
echo -e "\e[37mIt reads .supplemental-metadata.json files and applies the original timestamps to:\e[0m" |
|
echo -e "\e[37m1. EXIF metadata (DateTimeOriginal for photos, CreationDate for videos)\e[0m" |
|
echo -e "\e[37m2. Handles various file types: JPG, CR2, RAW, MP4, MOV, etc.\e[0m" |
|
echo "" |
|
echo -e "\e[32mUsage:\e[0m" |
|
echo -e " $0 \e[33msrcfolder\e[0m [\e[33moptions\e[0m]" |
|
echo "" |
|
echo -e "\e[32mParameters:\e[0m" |
|
echo -e " \e[33msrcfolder\e[0m Source directory containing files to process" |
|
echo "" |
|
echo -e "\e[32mOptions:\e[0m" |
|
echo -e " \e[33m-l logfile.log\e[0m Specify a log file to store processing information" |
|
echo -e " \e[33m-e exclude_pattern\e[0m Exclude files or directories that match the pattern" |
|
echo -e " (can be used multiple times)" |
|
echo -e " \e[33m-h, --help\e[0m Show this help message" |
|
echo "" |
|
echo -e "\e[32mEnvironment Variables:\e[0m" |
|
echo -e " \e[33mFIND_OPTS\e[0m Specify additional options for the find command" |
|
echo -e " (e.g., size limits, excluding specific directories)" |
|
echo "" |
|
echo -e "\e[32mExamples:\e[0m" |
|
echo -e " \e[37m# Basic usage\e[0m" |
|
echo -e " $0 /path/to/photos" |
|
echo "" |
|
echo -e " \e[37m# With logging\e[0m" |
|
echo -e " $0 /path/to/photos -l processing.log" |
|
echo "" |
|
echo -e " \e[37m# Exclude certain patterns\e[0m" |
|
echo -e " $0 /path/to/photos -e .DS_Store -e Thumbs.db" |
|
echo "" |
|
echo -e " \e[37m# Limit to specific file types\e[0m" |
|
echo -e " FIND_OPTS='-iname \"*.jpg\" -o -iname \"*.mp4\"' $0 /path/to/photos" |
|
echo "" |
|
echo -e "\e[32mRequirements:\e[0m" |
|
echo -e " \e[37m• exiftool\e[0m - For reading and writing EXIF metadata" |
|
echo -e " \e[37m• jq\e[0m - For parsing JSON metadata files" |
|
echo "" |
|
echo -e "\e[32mHow it works:\e[0m" |
|
echo -e " 1. \e[37mScans the source directory for image/video files\e[0m" |
|
echo -e " 2. \e[37mLooks for .supplemental-metadata.json files with the same name\e[0m" |
|
echo -e " 3. \e[37mExtracts original timestamps from the metadata\e[0m" |
|
echo -e " 4. \e[37mUpdates EXIF data in the original files (in place)\e[0m" |
|
echo -e " 5. \e[37mProvides progress tracking and statistics\e[0m" |
|
echo "" |
|
echo -e "\e[33m=========================================\e[0m" |
|
} |
|
|
|
init_log() { |
|
if [ -n "$logfile" ]; then |
|
echo "=========================================" >> "$logfile" |
|
echo "Processing started: $(date '+%Y-%m-%d %H:%M:%S')" >> "$logfile" |
|
echo "=========================================" >> "$logfile" |
|
fi |
|
} |
|
|
|
check_folders() { |
|
if [ ! -d "$srcfolder" ]; then |
|
echo -e "\e[31mSource folder does not exist\e[0m" |
|
exit 1 |
|
fi |
|
# No need to create subdirectories since we're processing in place |
|
} |
|
|
|
initialize_statistics() { |
|
# Build exclude pattern arguments for find command |
|
exclude_args=() |
|
for pattern in "${exclude_patterns[@]}"; do |
|
exclude_args+=" -not -ipath \"*$pattern*\" " |
|
done |
|
|
|
files_with_tags=0 |
|
files_without_tags=0 |
|
find_command="find \"$srcfolder\" $FIND_OPTS -type f ${exclude_args[@]} -print" |
|
if [ -n "$logfile" ]; then |
|
echo "Find command used for discovering files: $find_command" >> "$logfile" |
|
fi |
|
total_files=$(eval $find_command | wc -l) |
|
if [ -n "$logfile" ]; then |
|
echo "Total files found: $total_files" >> "$logfile" |
|
fi |
|
current_file=0 |
|
declare -gA tag_usage |
|
declare -gA filetype_count |
|
tmpfile=$(mktemp) |
|
trap "rm -f $tmpfile" EXIT |
|
|
|
# Colors for UI |
|
title_color="\e[33m" |
|
text_color="\e[37m" |
|
progress_color="\e[32m" |
|
box_color="\e[33m" |
|
header_color="\e[32m" |
|
reset_color="\e[0m" |
|
} |
|
|
|
# Function to get the best available date from a file |
|
get_best_date() { |
|
local file="$1" |
|
local date="" |
|
local used_tag="" |
|
|
|
# Try to get DateTimeOriginal first (most reliable for photos) |
|
date=$(exiftool -s3 -DateTimeOriginal "$file" 2>/dev/null) |
|
if [ -n "$date" ]; then |
|
used_tag="DateTimeOriginal" |
|
echo "$date|$used_tag" |
|
return 0 |
|
fi |
|
|
|
# Try SubSecDateTimeOriginal |
|
date=$(exiftool -s3 -SubSecDateTimeOriginal "$file" 2>/dev/null) |
|
if [ -n "$date" ]; then |
|
used_tag="SubSecDateTimeOriginal" |
|
echo "$date|$used_tag" |
|
return 0 |
|
fi |
|
|
|
# Try CreateDate |
|
date=$(exiftool -s3 -CreateDate "$file" 2>/dev/null) |
|
if [ -n "$date" ]; then |
|
used_tag="CreateDate" |
|
echo "$date|$used_tag" |
|
return 0 |
|
fi |
|
|
|
# Try SubSecCreateDate |
|
date=$(exiftool -s3 -SubSecCreateDate "$file" 2>/dev/null) |
|
if [ -n "$date" ]; then |
|
used_tag="SubSecCreateDate" |
|
echo "$date|$used_tag" |
|
return 0 |
|
fi |
|
|
|
# Try MediaCreateDate (for videos) |
|
date=$(exiftool -s3 -MediaCreateDate "$file" 2>/dev/null) |
|
if [ -n "$date" ]; then |
|
used_tag="MediaCreateDate" |
|
echo "$date|$used_tag" |
|
return 0 |
|
fi |
|
|
|
# Try SubSecMediaCreateDate |
|
date=$(exiftool -s3 -SubSecMediaCreateDate "$file" 2>/dev/null) |
|
if [ -n "$date" ]; then |
|
used_tag="SubSecMediaCreateDate" |
|
echo "$date|$used_tag" |
|
return 0 |
|
fi |
|
|
|
# Try GPSDateTime |
|
date=$(exiftool -s3 -GPSDateTime "$file" 2>/dev/null) |
|
if [ -n "$date" ]; then |
|
used_tag="GPSDateTime" |
|
echo "$date|$used_tag" |
|
return 0 |
|
fi |
|
|
|
# Try ModifyDate |
|
date=$(exiftool -s3 -ModifyDate "$file" 2>/dev/null) |
|
if [ -n "$date" ]; then |
|
used_tag="ModifyDate" |
|
echo "$date|$used_tag" |
|
return 0 |
|
fi |
|
|
|
# Try SubSecModifyDate |
|
date=$(exiftool -s3 -SubSecModifyDate "$file" 2>/dev/null) |
|
if [ -n "$date" ]; then |
|
used_tag="SubSecModifyDate" |
|
echo "$date|$used_tag" |
|
return 0 |
|
fi |
|
|
|
# Try GPSDateStamp |
|
date=$(exiftool -s3 -GPSDateStamp "$file" 2>/dev/null) |
|
if [ -n "$date" ]; then |
|
used_tag="GPSDateStamp" |
|
echo "$date|$used_tag" |
|
return 0 |
|
fi |
|
|
|
# Try FileModifyDate (filesystem date) |
|
date=$(exiftool -s3 -FileModifyDate "$file" 2>/dev/null) |
|
if [ -n "$date" ]; then |
|
used_tag="FileModifyDate" |
|
echo "$date|$used_tag" |
|
return 0 |
|
fi |
|
|
|
# If no date found, return empty |
|
echo "|" |
|
return 1 |
|
} |
|
|
|
# Function to get date from Google Photos supplemental metadata |
|
get_date_from_metadata() { |
|
local file="$1" |
|
local json_file="${file}.supplemental-metadata.json" |
|
|
|
if [ -f "$json_file" ]; then |
|
# Extract timestamp from JSON |
|
local ts=$(jq -r '.photoTakenTime.timestamp' "$json_file" 2>/dev/null) |
|
|
|
if [[ "$ts" =~ ^[0-9]+$ ]]; then |
|
# Convert timestamp to EXIF format |
|
local exif_date=$(date -u -d @"$ts" +"%Y:%m:%d %H:%M:%S" 2>/dev/null) |
|
if [ -n "$exif_date" ]; then |
|
echo "$exif_date|GooglePhotosMetadata" |
|
return 0 |
|
fi |
|
fi |
|
fi |
|
|
|
# If no metadata file or invalid timestamp, return empty |
|
echo "|" |
|
return 1 |
|
} |
|
|
|
# Function to normalize date format for EXIF |
|
normalize_date_for_exif() { |
|
local date="$1" |
|
|
|
# Remove timezone info if present |
|
date=$(echo "$date" | sed 's/[-+][0-9][0-9]:[0-9][0-9]$//') |
|
|
|
# Convert various formats to EXIF format (YYYY:MM:DD HH:MM:SS) |
|
# Handle ISO format: 2023-01-15T14:30:00 |
|
if [[ "$date" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2} ]]; then |
|
echo "$date" | sed 's/T/ /' | sed 's/-/:/g' |
|
return 0 |
|
fi |
|
|
|
# Handle standard format: 2023-01-15 14:30:00 |
|
if [[ "$date" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}[[:space:]][0-9]{2}:[0-9]{2}:[0-9]{2} ]]; then |
|
echo "$date" | sed 's/-/:/g' |
|
return 0 |
|
fi |
|
|
|
# Handle EXIF format: 2023:01:15 14:30:00 |
|
if [[ "$date" =~ ^[0-9]{4}:[0-9]{2}:[0-9]{2}[[:space:]][0-9]{2}:[0-9]{2}:[0-9]{2} ]]; then |
|
echo "$date" |
|
return 0 |
|
fi |
|
|
|
# Handle date only: 2023-01-15 |
|
if [[ "$date" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then |
|
echo "$date" | sed 's/-/:/g' | sed 's/$/ 00:00:00/' |
|
return 0 |
|
fi |
|
|
|
# If we can't parse it, return as is |
|
echo "$date" |
|
} |
|
|
|
# Function to set EXIF date based on file type |
|
set_exif_date() { |
|
local file="$1" |
|
local date="$2" |
|
local file_type="$3" |
|
|
|
# Normalize the date for EXIF |
|
local exif_date=$(normalize_date_for_exif "$date") |
|
|
|
case "$file_type" in |
|
"JPEG"|"TIFF"|"PNG"|"CR2"|"NEF"|"ARW"|"DNG"|"RAF"|"ORF"|"PEF"|"RW2") |
|
# For photos, use DateTimeOriginal |
|
if exiftool -q -overwrite_original -DateTimeOriginal="$exif_date" "$file" 2>/dev/null; then |
|
return 0 |
|
else |
|
# Fallback to CreateDate if DateTimeOriginal fails |
|
exiftool -q -overwrite_original -CreateDate="$exif_date" -ModifyDate="$exif_date" "$file" 2>/dev/null |
|
return $? |
|
fi |
|
;; |
|
"MP4"|"MOV"|"AVI"|"MKV"|"WEBM"|"3GP"|"M4V") |
|
# For videos, use CreationDate and MediaCreateDate |
|
exiftool -q -overwrite_original -CreationDate="$exif_date" -MediaCreateDate="$exif_date" "$file" 2>/dev/null |
|
return $? |
|
;; |
|
*) |
|
# For other files, try general date tags |
|
exiftool -q -overwrite_original -CreateDate="$exif_date" -ModifyDate="$exif_date" "$file" 2>/dev/null |
|
return $? |
|
;; |
|
esac |
|
} |
|
|
|
frame() { |
|
clear |
|
echo -e "${box_color}####################################################${reset_color}" |
|
echo -e "${box_color}# #${reset_color}" |
|
echo -e "${header_color}# Photo Timestamp Fixer #${reset_color}" |
|
echo -e "${box_color}# #${reset_color}" |
|
echo -e "${box_color}####################################################${reset_color}" |
|
} |
|
|
|
show_progress() { |
|
frame |
|
echo -e "${progress_color}Processing files... ($current_file/$total_files)${reset_color}" |
|
progress_percent=$(( (current_file * 100) / total_files )) |
|
progress_bar=$(printf "[%-50s]" $(eval "printf '='%.0s {1..$(( progress_percent / 2 ))}")) |
|
echo -e "${progress_color}Progress: ${progress_bar} ${progress_percent}%${reset_color}" |
|
|
|
# Multi-column layout for statistics |
|
echo -e "${box_color}----------------------------------------------------${reset_color}" |
|
echo -e "${header_color}Statistics:${reset_color}" |
|
echo -e "${text_color}Files with tags : $files_with_tags${reset_color}" |
|
echo -e "${text_color}Files without tags : $files_without_tags${reset_color}" |
|
echo -e "${box_color}----------------------------------------------------${reset_color}" |
|
|
|
# Tag statistics |
|
if [ ${#tag_usage[@]} -gt 0 ]; then |
|
echo -e "${header_color}Most used tags:${reset_color}" |
|
for tag in "${!tag_usage[@]}"; do |
|
echo -e "${text_color}$tag: ${tag_usage[$tag]}${reset_color}" |
|
done |
|
else |
|
echo -e "${text_color}No tags were found.${reset_color}" |
|
fi |
|
echo -e "${box_color}----------------------------------------------------${reset_color}" |
|
|
|
# File types processed |
|
if [ ${#filetype_count[@]} -gt 0 ]; then |
|
echo -e "${header_color}File types processed:${reset_color}" |
|
for ext in "${!filetype_count[@]}"; do |
|
echo -e "${text_color}$ext: ${filetype_count[$ext]}${reset_color}" |
|
done |
|
else |
|
echo -e "${text_color}No files were processed.${reset_color}" |
|
fi |
|
echo -e "${box_color}----------------------------------------------------${reset_color}" |
|
|
|
# Last processed files |
|
echo -e "${header_color}Last processed files:${reset_color}" |
|
tail -n 10 "$tmpfile" |
|
echo -e "${box_color}----------------------------------------------------${reset_color}" |
|
} |
|
|
|
start_processing() { |
|
# Build exclude pattern arguments for find command |
|
exclude_args=() |
|
for pattern in "${exclude_patterns[@]}"; do |
|
exclude_args+=" -not -ipath \"*$pattern*\" " |
|
done |
|
|
|
find_command="find \"$srcfolder\" $FIND_OPTS -type f ! -iname ".*" ${exclude_args[@]} -print" |
|
if [ -n "$logfile" ]; then |
|
echo "Find command used for discovering files: $find_command" >> "$logfile" |
|
fi |
|
total_files=$(eval $find_command | wc -l) |
|
|
|
while IFS= read -r file; do |
|
((current_file++)) |
|
|
|
process_file "$file" |
|
show_progress |
|
done < <(eval $find_command) |
|
} |
|
|
|
process_file() { |
|
local file=$1 |
|
|
|
extension="${file##*.}" |
|
((filetype_count[${extension,,}]++)) |
|
|
|
# First try to get date from Google Photos metadata file |
|
local date_info=$(get_date_from_metadata "$file") |
|
local datetime=$(echo "$date_info" | cut -d'|' -f1) |
|
local used_tag=$(echo "$date_info" | cut -d'|' -f2) |
|
|
|
# If no metadata file found, try to get date from EXIF |
|
if [ -z "$datetime" ]; then |
|
date_info=$(get_best_date "$file") |
|
datetime=$(echo "$date_info" | cut -d'|' -f1) |
|
used_tag=$(echo "$date_info" | cut -d'|' -f2) |
|
fi |
|
|
|
local tag_status="" |
|
|
|
if [ -n "$datetime" ]; then |
|
# File has date information |
|
((files_with_tags++)) |
|
tag_status="Tag present" |
|
|
|
# Track tag usage |
|
if [ -n "$used_tag" ]; then |
|
((tag_usage[$used_tag]++)) |
|
fi |
|
else |
|
# File has no date information - use current date as fallback |
|
((files_without_tags++)) |
|
tag_status="Tag added" |
|
datetime=$(date '+%Y:%m:%d %H:%M:%S') |
|
used_tag="CurrentDate" |
|
fi |
|
|
|
# Detect file type for proper EXIF handling |
|
local file_type=$(exiftool -FileType -b "$file" 2>/dev/null) |
|
if [ -z "$file_type" ]; then |
|
# Fallback to extension-based detection |
|
case "${extension,,}" in |
|
jpg|jpeg|tiff|png|cr2|nef|arw|dng|raf|orf|pef|rw2) |
|
file_type="JPEG" |
|
;; |
|
mp4|mov|avi|mkv|webm|3gp|m4v) |
|
file_type="MP4" |
|
;; |
|
*) |
|
file_type="Unknown" |
|
;; |
|
esac |
|
fi |
|
|
|
# Update EXIF data if needed |
|
if [ "$tag_status" == "Tag added" ]; then |
|
if set_exif_date "$file" "$datetime" "$file_type"; then |
|
tag_status="Tag added (EXIF updated)" |
|
else |
|
tag_status="Tag added (EXIF failed)" |
|
fi |
|
fi |
|
|
|
output_line="$(date '+%b %d %H:%M:%S') INFO: $file - EXIF DateTime: $datetime, Status: $tag_status, Used Tag: $used_tag" |
|
echo "$output_line" >> "$tmpfile" |
|
if [ -n "$logfile" ]; then |
|
echo "$output_line" >> "$logfile" |
|
fi |
|
} |
|
|
|
display_final_statistics() { |
|
frame |
|
echo -e "${progress_color}Processing completed. Files updated in place with timestamps if needed.${reset_color}" |
|
echo -e "${text_color}Files with proper tags: $files_with_tags${reset_color}" |
|
echo -e "${text_color}Files without tags: $files_without_tags${reset_color}" |
|
|
|
# Display most used tags and file types |
|
echo -e "${box_color}----------------------------------------------------${reset_color}" |
|
if [ ${#tag_usage[@]} -gt 0 ]; then |
|
echo -e "${header_color}Most used tags:${reset_color}" |
|
for tag in "${!tag_usage[@]}"; do |
|
echo -e "${text_color}$tag: ${tag_usage[$tag]}${reset_color}" |
|
done |
|
else |
|
echo -e "${text_color}No tags were found.${reset_color}" |
|
fi |
|
echo -e "${box_color}----------------------------------------------------${reset_color}" |
|
|
|
if [ ${#filetype_count[@]} -gt 0 ]; then |
|
echo -e "${header_color}File types processed:${reset_color}" |
|
for ext in "${!filetype_count[@]}"; do |
|
echo -e "${text_color}$ext: ${filetype_count[$ext]}${reset_color}" |
|
done |
|
else |
|
echo -e "${text_color}No files were processed.${reset_color}" |
|
fi |
|
echo -e "${box_color}----------------------------------------------------${reset_color}" |
|
} |
|
|
|
show_exit_prompt() { |
|
# Check if tput is available |
|
if command -v tput > /dev/null 2>&1; then |
|
# Centered overlay exit prompt using tput |
|
cols=$(tput cols) |
|
rows=$(tput lines) |
|
msg="Press any key to exit..." |
|
msg_length=${#msg} |
|
x=$(( (cols - 38) / 2 )) |
|
y=$(( (rows / 2) - 1 )) |
|
|
|
# Move cursor to the calculated position and display the box |
|
tput cup $y $x |
|
echo -e "${box_color}####################################${reset_color}" |
|
tput cup $((y + 1)) $x |
|
echo -e "${box_color}#${reset_color} ${text_color}${msg}${reset_color} ${box_color}#${reset_color}" |
|
tput cup $((y + 2)) $x |
|
echo -e "${box_color}####################################${reset_color}" |
|
tput cup $((y + 4)) 0 |
|
else |
|
# Simple exit prompt if tput is not available |
|
echo -e "${box_color}####################################${reset_color}" |
|
echo -e "${box_color}#${reset_color} ${text_color}Press any key to exit...${reset_color} ${box_color}#${reset_color}" |
|
echo -e "${box_color}####################################${reset_color}" |
|
fi |
|
read -n 1 -s |
|
} |
|
|
|
log_final_statistics() { |
|
if [ -n "$logfile" ]; then |
|
echo "=========================================" >> "$logfile" |
|
echo "Processing completed. Files updated in place with timestamps if needed." >> "$logfile" |
|
echo "=========================================" >> "$logfile" |
|
echo "Processing ended: $(date '+%Y-%m-%d %H:%M:%S')" >> "$logfile" |
|
echo "=========================================" >> "$logfile" |
|
echo "Files with proper tags: $files_with_tags" >> "$logfile" |
|
echo "Files without tags: $files_without_tags" >> "$logfile" |
|
echo "Most used tags:" >> "$logfile" |
|
for tag in "${!tag_usage[@]}"; do |
|
echo "$tag: ${tag_usage[$tag]}" >> "$logfile" |
|
done |
|
echo "File types processed:" >> "$logfile" |
|
for ext in "${!filetype_count[@]}"; do |
|
echo "$ext: ${filetype_count[$ext]}" >> "$logfile" |
|
done |
|
fi |
|
} |
|
|
|
# Run the main function |
|
main "$@" |
Even with the dates changes, it doesn't seem to matter at the moment. The dates aren't respected.