Help us measure site usage?

With your permission, we use self-hosted analytics to understand page usage. It is optional and does not affect the Service. Learn more.

PerlintermediatecliAI generated

Batch Image Renamer

Build a command-line Perl tool that renames photos based on the date and time they were taken, read from their EXIF metadata. For example, IMG_4821.JPG becomes 2024-07-14_18-32-05.jpg. If two photos share the same timestamp, the tool adds a suffix (_1, _2) instead of overwriting anything. Every rename is recorded in a small SQLite database through DBI, so you can undo the last run, and photos with GPS coordinates can get the place name added using LWP::UserAgent and a free geocoding API. The tool only shows what it would do by default and renames files only when you pass --apply. You'll practice reading EXIF data with Image::ExifTool, parsing command-line options, working safely with the filesystem, and using DBI and LWP.

Estimate
~14.5h
Steps
8
Completed by
0
Proposed by
codeseed.app

DBI · LWP::UserAgent

Project roadmap

  1. 01

    Set up the script and its options

    ~1.5h

    Create rename_photos.pl with use strict and use warnings. Parse options with Getopt::Long: a directory path (required), --apply (really rename; without it the script only prints what it would do), --recursive and --help. Print a usage message and exit with a non-zero code if the directory is missing or doesn't exist. List Image::ExifTool, DBI, DBD::SQLite and LWP::UserAgent in a cpanfile. Done when: --help, or an invalid path, prints a clear message.

  2. 02

    Find the image files

    ~1.5h

    Collect all files with the extensions .jpg, .jpeg, .png and .heic (case-insensitive) in the given directory, and in its subdirectories when --recursive is set, using File::Find. Skip hidden files and anything that isn't a regular file. Sort the list by path so every run is predictable. Done when: the script prints the images it found in a test folder.

  3. 03

    Read the capture date

    ~2h

    For each image, use Image::ExifTool to read DateTimeOriginal, falling back to CreateDate if it's missing. Turn the value (format 2024:07:14 18:32:05) into the new base name 2024-07-14_18-32-05 and keep the original extension in lowercase. Files with no date are left alone and reported as 'skipped: no EXIF date'. Done when: the dry run prints the old name and proposed new name for every image.

  4. 04

    Avoid name collisions

    ~2h

    Compute the final name of every file before renaming anything. If a target name is already taken, by an existing file or by another photo in the same run, add _1, _2 and so on until the name is free. A file that already has the correct name is left alone. Never overwrite a file. Done when: a test folder with three photos taken in the same second produces three different names in the dry run.

  5. 05

    Rename files and log them with DBI

    ~2h

    With --apply, rename the files and record each rename through DBI in a SQLite database (renames.db in the target folder), in a table with the run id, old path, new path and time. Wrap each run in a transaction so the log always matches what happened. Print a summary with the number of files renamed, skipped and failed. Done when: after an --apply run, the files are renamed and every rename is stored in renames.db.

  6. 06

    Add undo

    ~1.5h

    Add an --undo option that reads the most recent run from renames.db and renames each file back to its original name, in reverse order. If a file was moved or deleted since, report it and continue with the others. Mark the run as undone so it can't be undone twice. Done when: running --apply and then --undo restores the original file names.

  7. 07

    Add the place name with LWP::UserAgent

    ~2h

    Add a --with-place option. When a photo has GPS coordinates in its EXIF data, call the OpenStreetMap Nominatim reverse geocoding API with LWP::UserAgent and add the city name to the file name, e.g. 2024-07-14_18-32-05_Paris.jpg. Follow Nominatim's usage policy: send a descriptive User-Agent and make at most one request per second. If the request fails, rename without the place. Done when: photos with GPS data get a place name, and network errors don't stop the run.

  8. 08

    Write tests

    ~2h

    Write tests in t/ with Test::More, using a few small sample images with known EXIF dates committed as fixtures. Check the date-to-name conversion, the collision suffixes, that files without a date are skipped, that a dry run changes nothing on disk, and that undo restores the names. Run them with prove -l t/. Done when: prove passes.

Ready to build this?

Get a GitHub repo and start building. Your AI reviewer checks each step as you go.

~14.5h · 8 steps

Tech stack

DBILWP::UserAgent