Last active
July 5, 2020 18:05
-
-
Save JamesSwift/400035125985797db5ffbc8cf530fc87 to your computer and use it in GitHub Desktop.
Scan a directory and move or copy all files to a date-structured directory
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
This script will scan the directory you specify and move or copy | |
all files within it and its subdirectories. The files will be moved | |
to a subdirectory of the location where the script is, based on | |
either it's creation date or if the file contains exif data, the | |
date the photo was taken. | |
Call this script like so: | |
php import.php --dry-run --remove /home/me/Pictures | |
Flags: | |
--dry-run | |
Run the script but don't actually remove or copy any files | |
--quiet | |
Suppress all output expect errors | |
--remove | |
Remove the source files and folders after copying | |
Note: With this flag, any subdirectories in the source won't be | |
removed on the first run, but will be if you rin it again. This | |
is to prevent errors where files are in the process of being | |
copied or synchronized while the script is being run. | |
*/ | |
//Hide output | |
$quiet = !!array_search("--quiet", $argv); | |
//Dry-run | |
$dry_run = !!array_search("--dry-run", $argv); | |
//remove | |
$remove = !!array_search("--remove", $argv); | |
//Find dir | |
if (sizeof($argv)===1+intval($quiet)+intval($dry_run)+intval($remove)) die("Please specify a directory to import as the final argument.\n"); | |
$search = array_pop($argv); | |
//Suppress exif notices | |
error_reporting(E_ERROR); | |
//UNSAFE OPTION | |
//Instead of checking that images are identical before de-duplicating, | |
//If the filenames match, and the image dimensions match, assume the | |
//images are identical | |
$similar_image_deduplicate=false; | |
importPhotos($search); | |
function importPhotos($dir, $deleteDir=false){ | |
global $quiet, $dry_run, $remove, $similar_image_deduplicate; | |
if (!$quiet) print "\nImporting \e[0;30;42m$dir\e[0m\n"; | |
$list = scandir($dir); | |
//Delete empty directories (intentionally left over from previous run) | |
if (sizeof($list)===2 && $deleteDir===true){ | |
if (!$quiet) print "Deleted empty dir $dir\n"; | |
if (!$dry_run && $remove) rmdir($dir); | |
} | |
foreach ($list as $item){ | |
if ($item==="." || $item==="..") continue; | |
if (is_dir($dir."/".$item)){ | |
importPhotos($dir."/".$item, true); | |
continue; | |
} | |
//try to get exif data | |
$exifData = exif_read_data( $dir."/".$item, 'IFD0'); | |
if( $exifData !== FALSE && array_key_exists('DateTime', $exifData ) ) { | |
$date = strtotime( $exifData['DateTime'] ); | |
} else { | |
$date = filemtime($dir."/".$item); | |
} | |
$new_dir = date("Y/m F", $date); | |
//if (!$quiet) print $item." > ".$new_dir."\n"; | |
if (!is_dir($new_dir)){ | |
if (!$dry_run) mkdir($new_dir, 0770, true); | |
} | |
//Check for duplicates | |
if (file_exists($new_dir."/".$item)){ | |
//Check if dimensions are the same; | |
if ($similar_image_deduplicate && in_array(strtolower(pathinfo($dir."/".$item, PATHINFO_EXTENSION)), ["png","bmp","jpeg","jpg","gif"])){ | |
$imgsize = getimagesize($dir."/".$item); | |
$new_imgsize = getimagesize($new_dir."/".$item); | |
if ($imgsize['height'] === $new_imgsize['height'] && $imgsize['width'] === $new_imgsize['width']){ | |
if (!$quiet) print "duplicate filename skipped: ".red($dir."/".$item." = ".$new_dir."/".$item)."\n"; | |
if (!$dry_run && $remove) unlink($dir."/".$item); | |
continue; | |
} | |
print "\n\n---\n\n"; | |
} | |
//Compare hash of the files | |
if (hash("sha256", file_get_contents($dir."/".$item)) === hash("sha256", file_get_contents($new_dir."/".$item))){ | |
if (!$quiet) print "duplicate skipped: ".purple($dir."/".$item." === ".$new_dir."/".$item)."\n"; | |
if (!$dry_run && $remove) unlink($dir."/".$item); | |
continue; | |
} | |
//Files have same name, but aren't identical, rename the new file | |
$OriginalFilename = $dir."/".$item; | |
$filename = pathinfo($OriginalFilename, PATHINFO_FILENAME); | |
$extension = pathinfo($OriginalFilename, PATHINFO_EXTENSION); | |
$FinalFilename = $item; | |
$FileCounter = 1; | |
while (file_exists( $new_dir.'/'.$FinalFilename )){ | |
$FinalFilename = $filename . '_' . $FileCounter++ . '.' . $extension; | |
} | |
if (!$quiet) print "renaming due to conflict: ".$dir."/".$item." > ".cyan($new_dir."/".$FinalFilename)."\n"; | |
if (!$dry_run){ | |
if ($remove){ | |
rename($dir."/".$item, $new_dir."/".$FinalFilename); | |
} else { | |
copy($dir."/".$item, $new_dir."/".$FinalFilename); | |
} | |
} | |
} else { | |
if (!$quiet) print $dir."/".$item." > ".green($new_dir."/".$item)."\n"; | |
if (!$dry_run){ | |
if ($remove){ | |
rename($dir."/".$item, $new_dir."/".$item); | |
} else { | |
copy($dir."/".$item, $new_dir."/".$item); | |
} | |
} | |
} | |
} | |
} | |
//Helper colour functions | |
function green($txt){ return "\e[32m".$txt."\e[0m"; } | |
function cyan($txt){ return "\e[36m".$txt."\e[0m"; } | |
function purple($txt){ return "\e[35m".$txt."\e[0m"; } | |
function red($txt){ return "\e[31m".$txt."\e[0m"; } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment