#!/usr/bin/php -q
<?php
#============================================================================
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#============================================================================
# Copyright (C) 2012 Ryo Fujita
# Author: Ryo Fujita <rio@rio.st>
#============================================================================

// Get Metadata of RAW format file via Apple Script
function get_meta_as($filepath, $type){
	$args = 'tell application "Image Events"
	launch
	set this_image to open POSIX file "' . $filepath . '"
	tell this_image
		set the res to the value of metadata tag "' . $type . '"
	end tell
	close this_image
	return {res}
	end tell';
	exec("arch -i386 /usr/bin/osascript -e '$args'", $output, $return);
	return($output[0]);
}

// Get Metadata
// exif_read_data reports MIME types,
// but I hate to add "@" in PHP code to avoid warning when non image file was read
function get_meta($filepath){
	global $finfo;
	switch(finfo_file($finfo, $filepath)){
	case "image/jpeg":
	case "image/tiff":
		$exif = exif_read_data($filepath);
		if(isset($exif["DateTimeOriginal"])){
			$dt = $exif["DateTimeOriginal"];
		} else {
			$errmsg = "File '" . $filepath . "' does not include EXIF DateTime data. No operation was executed.\n";
		}
		if(isset($exif["Model"])){
			$md = $exif["Model"];
		} else {
			$errmsg = "File '" . $filepath . "' does not include EXIF Model data. No operation was executed.\n";
		}
		break;
	case "application/octet-stream":
	case "image/x-canon-cr2":
		if(SYSTEM == "Mac OS X"){ // If this script runs on Mac OS X, we can use Image IO via Apple Script
			if(!$dt = get_meta_as($filepath, "creation")){
				$errmsg = "Can't read DateTime from '" . $filepath . "' via Apple Script. No operation was executed.\n";
			}
			if(!$md = get_meta_as($filepath, "model")){
				$errmsg = "Can't read Model from '" . $filepath . "' via Apple Script. No operation was executed.\n";
			}
		} else if(SYSTEM == "Linux"){ // If this script runs on Linux, we can use ImageMagick module
			//
		} else {
			$errmsg = "File format of '" . $filepath . "' is not supported on this system. No operation was executed.\n";
		}
		break;
	default:
		$errmsg = "File '" . $filepath . "' is not an image file. No operation was executed.\n";
	}
	return array($dt, $md, $errmsg);
}

// Get OS string with phpinfo()
function get_os(){
	ob_start();
	phpinfo(INFO_GENERAL);
	$info_lines = explode("\n", ob_get_clean());
	foreach($info_lines as $line){
		if(preg_match("/Darwin Kernel/", $line)){
			return "Mac OS X";
		}
		if(preg_match("/Linux/", $line)){
			return "Linux";
		}
	}
}

// Build a new filename
function build_new_filename($datetime, $model){
	global $opts;
	$new_name = "";
	if($opts["prefix"]){ // PREFIX
		$new_name = $opts["prefix"];
	}
	if($opts["a"]){ // Add camera model name before or after datetime
		$new_name .= $datetime . "_" . $model;
	} else if($opts["b"]){
		$new_name .= $model . "_" . $datetime;
	} else {
		$new_name .= $datetime;
	}
	if($opts["suffix"]){ // SUFFIX
		$new_name .= $opts["suffix"];
	}
	return $new_name;
}

// Show Usage and exit
function usage(){
	$com_name = basename($_SERVER["PHP_SELF"]);
	echo
"
Usage: " . $com_name . " [OPTIONS] Directory

Options:
  --help, -h		Show this usage guide
  --verbose, -v		Show contextual information and format for easy reading
  -a			Camera model name follows EXIF date time. e.g. 20120101_EOS5D.JPG
  -b			Camera model name precedes EXIF date time. e.g. EOS5D_20120101.JPG
  -e			Lower case file extension
  -E			Upper case file extension
  -r			Rename original files, NOT copy (NOT SAFE)
  --prefix PREFIX	File prefix
  --suffix SUFFIX	File suffix
  --classify, -c 	Classify files into YYYY/MM/DD/ directories
  --original, -o 	Maintain original filename but classify
  --model, -m 		Classify files into YYYY/MM/DD/Model/ directories
"	
	;
	exit(0);
}

define("VERSION", "0.5"); // Script version
define("SYSTEM", get_os()); // check which OS on where this script is running

// check specification of directory
if(!isset($_SERVER["argv"][1])){
	echo("No Directory is specified!\n");
	usage();
}

// definitions of options
$shortopts = "";
$shortopts .= "a";
$shortopts .= "b";
$shortopts .= "e";
$shortopts .= "E";
$shortopts .= "r";
$shortopts .= "h";
$shortopts .= "v";
$shortopts .= "c";
$shortopts .= "o";
$shortopts .= "m";

$longopts = array(
	"help",
	"verbose",
	"suffix:",
	"prefix:",
	"classify",
	"original",
	"model",
);

// check options
$opts = getopt($shortopts, $longopts);
$opts["a"] = isset($opts["a"]);
$opts["b"] = isset($opts["b"]);
$opts["e"] = isset($opts["e"]);
$opts["E"] = isset($opts["E"]);
$opts["r"] = isset($opts["r"]);
$opts["h"] = isset($opts["help"]) || isset($opts["h"]);
$opts["v"] = isset($opts["verbose"]) || isset($opts["v"]);
$opts["c"] = isset($opts["classify"]) || isset($opts["c"]);
$opts["o"] = isset($opts["original"]) || isset($opts["o"]);
$opts["m"] = isset($opts["model"]) || isset($opts["m"]);
if($opts["h"]){ // Show usage and exit
	usage();
}
if($opts["a"] && $opts["b"]){
	echo("You can specify only one of options, '-a' and '-b'.\n");
	usage();
}
if($opts["e"] && $opts["E"]){
	echo("You can specify only one of options, '-e' and '-E'.\n");
	usage();
}
if($opts["o"] && !$opts["c"]){
	echo("Both '--original' and '--classify' options are needed.\n");
	usage();
}
if($opts["m"] && !$opts["c"]){
	echo("Both '--model' and '--classify' options are needed.\n");
	usage();
}

// check existance of directory
$dir = realpath($_SERVER["argv"][$_SERVER["argc"] - 1]);
if(!file_exists($dir)){
	echo("Can't locate specified directory!\n");
	usage();
}

// check whether $dir is a directory
if(!is_dir($dir)){
	echo("Specified path seems a simple file, NOT a directory!\n");
	usage();
}

// Instantiate finfo from PECL
$finfo = finfo_open(FILEINFO_MIME_TYPE);

// Preapare an associated array to store combinations of source and destination
// Array consists of destination, sequence #, file extension
$filepaths = array();

// Glob files and build combinations of source and destination
$tgt = $dir . "/*";
foreach(glob($tgt) as $filepath){
	if(is_dir($filepath)){
		continue;
	}
	list($dt, $md, $errmsg) = get_meta($filepath);
	if(isset($errmsg)){
		echo($errmsg);
		continue;
	}
	$yyyy = substr($dt, 0, 4); $mm = substr($dt, 5, 2); $dd = substr($dt, 8, 2); $HH = substr($dt, 11, 2); $MM = substr($dt, 14, 2); $SS = substr($dt, 17, 2); //preg_match is better?
	$pathinfo = pathinfo($filepath);
	if($opts["o"]){ // "original" option keeps original filename
		$new_name = $pathinfo["filename"];
		$filepaths[$filepath]["ext"] = $pathinfo["extension"];
	} else {
		$new_name = build_new_filename($yyyy . $mm . $dd . $HH . $MM . $SS, preg_replace("/[ ]/", "", $md)); // Build a new name
		if($opts["e"]){
			$filepaths[$filepath]["ext"] = strtolower($pathinfo["extension"]);
		} else if($opts["E"]){
			$filepaths[$filepath]["ext"] = strtoupper($pathinfo["extension"]);
		} else {
			$filepaths[$filepath]["ext"] = $pathinfo["extension"];
		}
	}
	$filepaths[$filepath]["dest"] = $dir;
	if($opts["c"]){ // "classify" option makes recursive directories
		if($opts["m"]){ // "model" option adds /model/ subdirectory
			$filepaths[$filepath]["dest"] .= "/" . $yyyy . "/" . $mm . "/" . $dd . "/" . $md;
		} else {
			$filepaths[$filepath]["dest"] .= "/" . $yyyy . "/" . $mm . "/" . $dd;
		}
	}
	$filepaths[$filepath]["dest"] .= "/" . $new_name;
}

// Add Sequence Number
$keys = array_keys($filepaths);
$cnt = count($keys);
for($i = 0; $i < $cnt - 1; $i++){
	if(!isset($filepaths[$keys[$i]]["seq"])){
		$max = 1;
		$items_to_be_updated = array();
		for($j = $i + 1; $j < $cnt; $j++){
			if(!strcmp($filepaths[$keys[$i]]["dest"], $filepaths[$keys[$j]]["dest"])){
				if(!isset($filepaths[$keys[$i]]["seq"])){
					$filepaths[$keys[$i]]["seq"] = 1;
					$items_to_be_updated[] = $i;
				}
				$max++;
				$filepaths[$keys[$j]]["seq"] = $max;
				$items_to_be_updated[] = $j;
			}
		}
		foreach($items_to_be_updated as $item){
			$filepaths[$keys[$item]]["max"] = $max;
		}
	}
}

// Operations are executed actually.
foreach($filepaths as $src=>$v){
	$dest = sprintf("%s%s.%s", $v["dest"], isset($v["seq"]) ? "_" . sprintf("%0" . sprintf("%d", floor(log10($v["max"])) + 1) . "d", $v["seq"]) : "", $v["ext"]);
	@mkdir(dirname($dest), 0755, true);
	if($opts["r"]){ // rename
		rename($src, $dest);
	} else { // copy [default]
		copy($src, $dest);
	}
	// Verbose option reports progression
	if($opts["v"]){
		printf("%s %s to %s ...\n", $opts["r"] ? "Renaming" : "Copying", $src, $dest);
	}
}
?>
