Skip to content

Latest commit

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Bulldozer

Bulldozer is a small PHP CLI script that organizes images into yyyy/mm/dd directories based on the image date.

It targets PHP 8.2 or newer and has no Composer dependencies.

By Colin Devroe

Setup

Edit the configuration block at the top of bulldozer.php:

$config = [
'source' => '/path/to/images_to_organize',
'locations' => [
'default' => '/path/to/image_library',
'backup' => '/Volumes/BackupDrive/image_library',
],
];

The source and destination directories must already exist. Bulldozer creates the dated yyyy/mm/dd directories inside the destination as needed.

Config File

You can also put configuration in JSON and load it with --config:

{
"source": "/path/to/images_to_organize",
"destination": "/path/to/image_library",
"date": "mtime",
"timezone": "America/New_York",
"duplicates": "rename"
}

Run with:

php bulldozer.php --config=/path/to/bulldozer.config.json --json

CLI flags override config-file values. For safety, config files cannot enable --run; write operations still require the explicit --run CLI flag.

Usage

Always start with a dry run:

php bulldozer.php

Actually copy files:

php bulldozer.php --run

Move files instead of copying them:

php bulldozer.php --mode=move --run

Use a configured destination:

php bulldozer.php --location=backup --run

Override the configured source and destination:

php bulldozer.php --source=/path/to/source --destination=/path/to/library --run

Legacy usage still works:

php bulldozer.php backup true

Options

--config=PATH Load a JSON config file. CLI flags override config values.
--source=PATH Source directory. Defaults to the configured source.
--destination=PATH Destination directory. Overrides --location.
--location=NAME Configured destination name. Default: default.
--run Actually copy or move files. Default is dry run.
--dry-run Force dry run.
--mode=copy|move Copy or move files. Default: copy.
--date=exif|mtime Date source. Default: exif with mtime fallback.
--duplicates=rename|skip Rename or skip filename collisions. Default: rename.
--timezone=TIMEZONE Timezone for EXIF and mtime dates.
--all-files Process every file except ignored system files.
--json Print machine-readable JSON instead of text summary.
--manifest=PATH Write a JSON manifest of planned or completed actions.
--quiet Suppress verbose per-file output.
--verbose Print per-file progress. Ignored when --json is used.
--log[=PATH] Write a log file.
--hooks[=PATH] Load a PHP hooks file.
--help Show help.

Agent Usage

Agents should dry-run first and use JSON plus a manifest:

php bulldozer.php \
--source=/path/to/source \
--destination=/path/to/library \
--date=mtime \
--timezone=America/New_York \
--json \
--manifest=/tmp/bulldozer-manifest.json

Then inspect the manifest before running:

php bulldozer.php \
--source=/path/to/source \
--destination=/path/to/library \
--date=mtime \
--timezone=America/New_York \
--json \
--manifest=/tmp/bulldozer-manifest.json \
--run

Agent-specific guidance lives in AGENTS.md. More detailed references are in docs/cli-reference.md, docs/manifest-schema.md, docs/hooks.md, and docs/agent-examples.md.

Exit Codes

0 Success.
1 Usage, configuration, or validation error.
2 Run completed with file operation errors.
3 Run completed with hook, log, or manifest reporting errors.

Dates

By default, Bulldozer tries EXIF image dates first:

  1. DateTimeOriginal
  2. DateTimeDigitized
  3. DateTime

If no usable EXIF date is found, it falls back to filesystem modified time.

Use --date=mtime to skip EXIF and use modified time for every file.

Use --timezone=America/New_York if you want to force the timezone used when building date folders. Without this option, Bulldozer uses PHP's configured default timezone.

Duplicates

The default duplicate behavior is rename:

IMG_0001.jpg
IMG_0001-1.jpg
IMG_0001-2.jpg

Use --duplicates=skip to leave colliding files untouched.

When duplicates are skipped, Bulldozer records whether the files appear to be the same by comparing file size first, then SHA-256 hashes only when sizes match.

Logging

Use --log to write a basic operation log:

php bulldozer.php --log --run

That writes to bulldozer.log next to the script. You can also choose a path:

php bulldozer.php --log=/tmp/bulldozer.log --run

The log includes run configuration, skipped files, directory creation, copy/move operations, duplicate handling, hook errors, and final stats.

Hooks

A full plugin system would add too much complexity for a small CLI script. The script instead supports a deliberately small event hook file for developers who need to run extra code as files are planned, copied, moved, skipped, or when a run starts or finishes.

Create a hook file:

<?phpreturnstaticfunction (BulldozerHooks$hooks): void {
$hooks->on('file.copied', staticfunction (array$event): void {
echo"Copied {$event['source']} to {$event['destination']}" . PHP_EOL;
});
};

Run with hooks:

php bulldozer.php --hooks=/path/to/bulldozer-hooks.php --run

Available events:

run.start
directory.planned
directory.created
directory.error
file.planned
file.copied
file.moved
file.duplicate
file.skipped
file.error
run.finish

Hook failures are logged and counted as errors, but they do not stop the run.

Notes

  • The script is intended for images: JPG, PNG, HEIC, TIFF.
  • EXIF reading is available for formats supported by PHP's exif extension, typically JPEG and TIFF. HEIC files usually fall back to modified time.
  • Use --all-files if you want Bulldozer to process non-image files too.
  • Move mode uses rename() first, then falls back to copy-and-delete when moving across filesystems.

Version History

  • 2026.05 - May 5, 2026
    • Target PHP 8.2+.
    • Added dry-run by default, explicit --run, copy/move modes, safer duplicate handling, EXIF date support, streaming traversal, logging, and optional event hooks.
    • Added agent-ready JSON output, manifest output, stable exit codes, CLI reference docs, and a smoke test.
  • 2021.01 - January 25, 2021
    • Initial public release.

About

A simple PHP script to move images from one directory into /yyyy/mm/dd directories based on date created.

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages