Automating Media File Renaming: A Practical Bash Scripting Project
Software development
6 August 2025

Automating Media File Renaming: A Practical Bash Scripting Project

As part of my project documentation, I wanted to share a small but meaningful script I created during the file cleanup phase of one of my media archiving tasks.

The issue may seem minor at first glance: renaming a batch of media files, but the underlying process demonstrates how scripting can transform repetitive, error prone manual work into a reliable, automated workflow.

The Problem

While consolidating media assets for a documentation project, I encountered a folder full of photos and videos collected over several years from different devices. The files were inconsistently named: IMG_0012.JPG, Screenshot 2023 04 17 at 10.34.21.png, MOV_0001.mov, and so on.

I wanted a naming convention that:

  • Used the file's true creation timestamp (not the modified or access time)
  • Ensured each filename was unique
  • Included standardized formatting: DD_Mon_YYYY_at_HH.MM.SS
  • Worked across typical media types: images and videos
  • Was idempotent: re running the script wouldn't cause unwanted renames

The Solution: A Bash Script with macOS Metadata Support

To address this, I wrote a Bash script that:

  • Iterates over all files in a folder
  • Filters only media files (jpg, jpeg, png, gif, mov, mp4, avi)
  • Retrieves the file system creation date using the macOS mdls command
  • Converts the timestamp to Asia Jakarta (WIB) time zone
  • Constructs a unique filename like 07_Aug_2025_at_13.42.10_1.JPG, always appending a suffix
  • Skips non file items and unsupported file types
  • Logs all actions to both terminal and a file (renamed_log_always_suffixed.txt)

This script is designed for macOS only, due to its reliance on mdls and BSD style date.

Key Features

  • Uses the original file creation date from macOS metadata (not the modified or access time)
  • Adjusts timestamps to Asia Jakarta timezone (UTC plus 7)
  • Accepts only common image and video formats
  • Always appends a numeric suffix starting from _1
  • Avoids renaming files that are already correctly named
  • Writes detailed logs using tee for traceability

Sample Output

For a file originally named IMG_1234.JPG created on 7 August 2025 at 13:42:10, the script will rename it as:

07_Aug_2025_at_13.42.10_1.JPG

If a file with that name already exists, it tries:

07_Aug_2025_at_13.42.10_2.JPG

and so on, until it finds a unique name.

Why This Matters in the Project

This script became part of the final data preparation pipeline for a larger documentation and archival system. Proper naming is essential for:

  • Indexing media files in static websites or content management systems
  • Maintaining chronological order across files from different sources
  • Preventing overwrites and accidental loss due to filename duplication

By automating the rename process, I was able to:

  • Reduce manual error
  • Save time during migration
  • And ensure consistent formatting for downstream tooling

Source

You can view or adapt the script below. It is designed to be run in a Bash shell on macOS, and it assumes you have the necessary permissions to rename files in the target directory.:

bash <<'EOF' | tee renamed_log_always_suffixed.txt
# Set shell options for case-insensitive globbing and matching hidden files (if needed)
# For Bash:
shopt -s nocaseglob # Case-insensitive globbing
# For Zsh (often default for new users, but explicitly set if needed):
# setopt KSH_GLOB # or setopt NO_CASE_GLOB, NO_MARK_DIRS, etc.
# The `for f in *` then checking extension is generally safer and more portable for this.

# Process all files in the current directory
for f in *; do
  # Check if it's a regular file
  if [[ ! -f "$f" ]]; then
    echo "--- Skipping non-file: $f ---"
    continue
  fi

  # Extract extension in lowercase for easier checking
  ext_lower=$(echo "${f##*.}" | tr '[:upper:]' '[:lower:]')

  # List the extensions you want to process
  case "$ext_lower" in
    jpg|jpeg|png|gif|mov|mp4|avi)
      # This is a file type we want to process
      ;;
    *)
      # Not a supported media file type, skip it
      echo "--- Skipping unsupported file type: $f (extension .$ext_lower) ---"
      continue
      ;;
  esac

  echo "--- Processing: $f ---"

  # Get macOS file system creation date
  fs_date_raw=$(mdls -name kMDItemFSCreationDate -raw "$f")

  # Ensure fs_date_raw is not empty. If mdls fails, skip processing this file.
  if [[ -z "$fs_date_raw" ]]; then
      echo "Error: Could not retrieve file system date for $f. Skipping rename."
      echo ""
      continue
  fi

  # Set TZ to Western Indonesian Time (WIB) which is UTC+7 for formatting the date
  export TZ='Asia/Jakarta'

  # Format the file system date for the base part of the new name: "DD_Mon_YYYY_at_HH.MM.SS"
  base_name_prefix=$(date -j -f "%Y-%m-%d %H:%M:%S %z" "$fs_date_raw" "+%d_%b_%Y_at_%H.%M.%S")

  # Unset TZ
  unset TZ

  # Original file extension (maintains original case for the final rename)
  original_ext="${f##*.}"

  # Initialize the suffix number. It will *always* start at 1 for the first attempt.
  current_suffix_number=1
  final_target_name=""

  # Loop to find a unique filename for the current file ($f)
  while true; do
    # Candidate name will always include a suffix starting from _1
    candidate_name="${base_name_prefix}_${current_suffix_number}.${original_ext}"

    # --- Check if the candidate name is available or if it's already our file ---

    # 1. If the candidate name does NOT exist on disk, we found a unique name.
    if [[ ! -e "$candidate_name" ]]; then
      final_target_name="$candidate_name"
      break
    fi

    # 2. If the candidate name DOES exist, check if it's actually the current file ($f).
    # This handles cases where the script is run multiple times, and files are already renamed.
    if [[ "$f" -ef "$candidate_name" ]]; then
      final_target_name="$f"
      break
    fi

    # 3. If the candidate name exists and is NOT the current file ($f),
    # then it's a collision with another file. Increment the suffix number and try again.
    ((current_suffix_number++))
  done

  # --- Perform the rename action ---
  if [[ "$f" != "$final_target_name" ]]; then
      mv "$f" "$final_target_name"
      echo "Renamed: $f -> $final_target_name"
  else
      echo "No change: $f (already has the target name or no unique name could be determined)."
  fi

  echo ""

done
EOF