#!/usr/bin/php /dev/null & ",$cmd)." & wait"; echo $echo; if (strlen($echo) > 1) { echo "\n"; } exec($exec); } } function getdng($jpg) { $ext = pathinfo($jpg,PATHINFO_EXTENSION); $check = glob("*/".basename($jpg,$ext)."DNG"); if (isset($check[0])) { return $check[0]; } else { return null; } } function getjpg($dng) { $ext = pathinfo($dng,PATHINFO_EXTENSION); $check = glob("*/".basename($dng,$ext)."JPG"); if (isset($check[0])) { return $check[0]; } else { return null; } } function fin($print = null) { if (isset($print)) { echo "\n".$print; } echo "\nFinished in ".floor($time = microtime(true)-$_SERVER["REQUEST_TIME_FLOAT"])." seconds\n"; die; } function ask($text) { echo $text."\n"; return trim(fgets(fopen("php://stdin","r"))); } function msg($text, $action = null) { if ($action == 1) { echo bashcolor($text."\n","red"); die; } elseif ($action == 2) { echo bashcolor($text." (Y/n)\n","blue"); $line = trim(fgets(fopen("php://stdin","r"))); $line = $line ?: "y"; if($line != "y"){ echo bashcolor("Exiting!\n","red"); die; } } else { echo $text."\n"; } } function args($query) { global $argv; foreach ($argv as $value) { $parts = explode("=", $value); $opt[trim($parts[0],"-")] = isset($parts[1]) ? $parts[1] : 1; } if ($query == "app" && isset($argv[1])) { return $argv[1]; } elseif ($query == "dir" && count($argv) > 2) { return chop($argv[count($argv)-1], "/")."/"; } elseif (isset($opt[$query])) { return $opt[$query]; } else { return null; } } function Welcome($msg) { global $version; $out = "\033[1;1H\033[2J"; // clear screen $out .= bashcolor("Leaf Version: ".$version, "white", "magenta")." ".shell_exec("date"); $out .= $msg."\n\n"; return $out; } function getClosest($search, $arr) { $closest = null; foreach ($arr as $item) { if ($closest === null || abs($search - $closest) > abs($item - $search)) { $closest = $item; } } return $closest; } $colors = array_merge(array("blue","green","cyan","red","purple","brown","yellow","light blue","light green", "light cyan", "light red", "light purple"),array_fill(0, 500, "light gray")); function bashcolor($str,$fgcolor="white",$bgcolor=null) { static $fgcolors = array('black' => '0;30', 'dark gray' => '1;30', 'blue' => '0;34', 'light blue' => '1;34', 'green' => '0;32', 'light green' => '1;32', 'cyan' => '0;36', 'light cyan' => '1;36', 'red' => '0;31', 'light red' => '1;31', 'purple' => '0;35', 'light purple' => '1;35', 'brown' => '0;33', 'yellow' => '1;33', 'light gray' => '0;37', 'white' => '1;37', 'underline' => '4'); static $bgcolors = array('black' => '40', 'red' => '41', 'green' => '42', 'yellow' => '43', 'blue' => '44', 'magenta' => '45', 'cyan' => '46', 'light gray' => '47'); $out=""; if (!isset($fgcolors[$fgcolor])) { $fgcolor='white'; } if (!isset($bgcolors[$bgcolor])) { $bgcolor=null; } if ($fgcolor) { $out .= "\033[{$fgcolors[$fgcolor]}m"; } if ($bgcolor) { $out .= "\033[{$bgcolors[$bgcolor]}m"; } $out .= $str."\033[0m"; return $out; } // Checks if (args("dir") && !is_dir(args("dir"))) { msg("Problem with working dir: ".args("dir"), 1); } if (!is_dir("scratch")) { mkdir("scratch"); } //////////////////////////////////////////////////////////////////////////////////////////////// // Note: Help ////////////////////// if (!args("app") | args("app") == "help" | args("app") == "-help" | args("app") == "--help") { ///////////////////////////////////////////////////////////////////////// $help = "Leaf $version USAGE: leaf [mode] [-options] directory Modes: clean remove scratch files crop define EXIF crop values using template files -crops= specify how many files to use desort remove image sequence prefix deskew detect rotation angles -max= angles greater than this value are ignored, default .4 -pad= pixels to pad around crop area, default 80 -contrast= contrast boost, default 20 -size= size in pixels of dmap, default 2200 divide wrapper for imagemagick Divide_Src -map= specify brightness file -adjust= levels adjustment (ex. \"0%,98%,.9\") -q= quality out of 100 dupes Find duplicate images using computed PHASH on thumbnails -threshold= match threshold -walk= comparison scope (compare n image to n, n+1, n+2, etc) makepdf combine images into a pdf with img2pdf profile apply xmp profile to images (requires exiv2 > 0.25) -file= xmp profile resort reorder image sequence by adding a new image -file= file to insert -x= position of inserted file review print a table of image dimension statistics rotate batch transform rotate (lossy) -x= rotation angle, default=90 -q=<0-100> jpeg quality, default read from source or 95 setdpi set image dpi with exiftool -x= specify dpi -height= calculate dpi from specified height sort sort files as AAABBB -> ABABAB -m= specify midpoint (cover image) strip strip exif crop values from images with exiftool "; echo $help; fin(); //////////////////////////////////////////////////////////////////////////////////////////////// // Note: Clean ////////////////////// } elseif (args("app") == "clean") { echo Welcome("Remove scratch dir"); ///////////////////////////////////////////////////////////////////////// msg("Remove scratch files?",2); if (!is_dir("scratch")) { msg("Problem with scratch dir",2); } else { foreach (glob("scratch/*.*") as $file) { unlink($file); } } fin(); //////////////////////////////////////////////////////////////////////////////////////////////// // Note: Rotate ////////////////////// } elseif (args("app") == "rotate") { echo Welcome("Batch rotate (lossy)"); ///////////////////////////////////////////////////////////////////////// $default_quality = 95; $files = glob(args("dir")."*.{jpg,JPG,tif,TIF}", GLOB_BRACE); if (args("x")) { $angle = args("x"); } else { $angle = 90; } $dest = rtrim(args("dir"), '/')."_rotated"; if (count(glob($dest."/*.*"))) { msg("Files already exist in destination ".$dest,1); } elseif (!is_dir($dest)) { mkdir($dest); } foreach ($files as $file) { $ext = pathinfo($file,PATHINFO_EXTENSION); $output = $dest."/".basename($file,$ext)."jpg"; if (args("q")) { $quality = args("q"); } elseif (strtolower($ext) == "jpg") { $quality = @exec("identify -format '%Q' ".$file); } else { $quality = $default_quality; } $msg = "Rotating ".$file." ".$angle." to ".$output." with Q=".$quality; $cmd = "convert -rotate ".$angle." -quality ".$quality." ".$file." ".$output; $thread[] = array($msg, $cmd); } msg("Beginning multithreaded convert with ".$xt." threads"); multiexec($thread,$xt); fin(); //////////////////////////////////////////////////////////////////////////////////////////////// // Note: Profile ////////////////////// } elseif (args("app") == "profile") { echo Welcome("Apply XMP profile to images"); ///////////////////////////////////////////////////////////////////////// $files = glob(args("dir")."*.*"); if (!file_exists(args("file"))) { msg("Error reading xmp profile",1); } else { $profile = args("file"); } echo $profile.": ".date("F d, Y", filemtime($profile))." (".count(file($profile))." lines)\n\n"; $lines = file_get_contents($profile); $check[] = "crs:RawFileName"; $check[] = "crs:WhiteBalance"; $check[] = "crs:Temperature"; $check[] = "crs:Tint"; $check[] = "crs:IncrementalTemperature"; $check[] = "crs:IncrementalTint"; $check[] = "crs:Sharpness"; $check[] = "crs:LensProfileName"; foreach ($check as $query) { preg_match("/^.*".$query.".*\$/m",$lines,$matches); if ($matches) { echo trim($matches[0])."\n"; } } echo "\n"; msg("This operation will overwrite all existing metadata for target files. Continue?",2); foreach ($files as $file) { msg("Applying ".$profile." to ".$file); @exec("cat ".$profile." | exiv2 -iXX- ".$file); } fin(); //////////////////////////////////////////////////////////////////////////////////////////////// // Note: Sort ////////////////////// } elseif (args("app") == "sort") { echo Welcome("Sort files AAABBB -> ABABAB"); ///////////////////////////////////////////////////////////////////////// $files = glob(args("dir")."*.*"); if (args("m")) { $check = glob(args("dir")."*".args("m")."*"); if (!$check) { msg("Cannot find midpoint file to match ".args("m"),1); } $midpoint_key = args("m"); } else { $mid = $files[ceil(count($files)/2)]; echo "Guessing midpoint key from ".$mid."\n"; $midpoint_key = filter_var($mid, FILTER_SANITIZE_NUMBER_INT); } $e = 2; $o = 1; foreach ($files as $file) { $num = filter_var($file, FILTER_SANITIZE_NUMBER_INT); if ($num < $midpoint_key) { $new = args("dir").sprintf('%03d',$e)."-".basename($file); $ops[$e] = array($file,$new,8); $e = $e+2; } else { $new = args("dir").sprintf('%03d',$o)."-".basename($file); $ops[$o] = array($file,$new,6); $o = $o+2; } } ksort($ops); foreach ($ops as $op) { msg(bashcolor($op[0],"green")." will be renamed ".bashcolor($op[1],"cyan").", orientation=".bashcolor($op[2],"purple")); } $range = range(2,count($ops)/2+2); $length = count($range); $quarter = count($range)/4; $most = array_slice($range, 1, $length-$quarter, 1); $rest = array_slice($range, $length-$quarter, $quarter, 1); $picks = array_merge(array_rand($most, 2), array_rand($rest, 8)); echo "\n"; if (ask("Create spreads? (Y/n)") != "n") { echo "Creating spreads: "; foreach($picks as $key) { $left = $ops[($key*2)]; $right = $ops[($key*2)+1]; $ext = pathinfo($left[0],PATHINFO_EXTENSION); $lfile = $left[0]; $rfile = $right[0]; $tmpleft = "/tmp/".basename($left[1]); $tmpright = "/tmp/".basename($right[1]); if ($ext == "DNG" && getjpg($left[0]) && getjpg($right[0])) { $lfile = getjpg($left[0]); $rfile = getjpg($right[0]); $tmpleft = "/tmp/".basename($left[1],$ext)."JPG"; $tmpright = "/tmp/".basename($right[1],$ext)."JPG"; } elseif ($ext == "DNG") { echo "-"; continue; } copy($lfile,$tmpleft); copy($rfile,$tmpright); exec("exiftool -overwrite_original -n -Orientation=".$left[2]." ".$tmpleft."; exiftool -overwrite_original -n -Orientation=".$right[2]." ".$tmpright."; "); exec("montage -label '%f' -font Helvetica -pointsize 20 -background '#000000' -fill 'white' -define jpeg:size=1000x1000 -geometry 1000x1000+2+2 -auto-orient ".$tmpleft." ".$tmpright." /tmp/contact_sheet_".$key.".jpg"); $sheets[] = "/tmp/contact_sheet_".$key.".jpg"; echo "."; } echo "\n\n"; if (isset($sheets)) { exec("xattr -c ".implode(" ", $sheets)); $term = chop(shell_exec("env | grep 'TERM_PROGRAM=' | cut -f2- -d=")); if ($term == "iTerm.app") { echo shell_exec("imgcat ".implode(" ", $sheets)); } else { exec("open ".implode(" ", $sheets)." -b com.apple.Preview"); } } else { msg("Spread creation failed"); } msg("Continue?",2); } echo "Moving files: "; foreach ($ops as $key => $op) { rename($op[0],$op[1]); exec("exiftool -overwrite_original -n -Orientation=".$op[2]." ".$op[1]); echo "."; } fin(); //////////////////////////////////////////////////////////////////////////////////////////////// // Note: Desort ////////////////////// } elseif (args("app") == "desort") { echo Welcome("Remove image sequence"); ///////////////////////////////////////////////////////////////////////// $files = glob(args("dir")."[0-9][0-9][0-9]-*"); foreach ($files as $file) { $ops[] = array($file, args("dir").substr(basename($file),4)); } foreach ($ops as $parts) { if (file_exists($parts[1])) { msg("Meltdown! Renamed file would overwrite ".$parts[1],1); } } foreach ($ops as $parts) { echo "Renaming ".$parts[0]." to ".$parts[1]."\n"; rename($parts[0], $parts[1]); } fin(); //////////////////////////////////////////////////////////////////////////////////////////////// // Note: Crop ////////////////////// } elseif (args("app") == "crop") { echo Welcome("Define crop areas and apply to EXIF tags"); ///////////////////////////////////////////////////////////////////////// if (args("crops")) { $multicrops = args("crops"); } else { $multicrops = 6; } $files = glob(args("dir")."*.*"); if ($multicrops > 1) { foreach ($files as $file) { $key = filter_var(substr(basename($file), 4), FILTER_SANITIZE_NUMBER_INT); $seq[$key] = $file; } ksort($seq); $seg = floor(count($seq)/$multicrops)-1; $picks = array(); foreach (range(1, $multicrops, 1) as $val) { $picks[] = array_values(array_slice($seq, $val*$seg, 1))[0]; } } else { $picks[] = $files[floor(count($files)*.7)]; } msg("Files tagged for crop:\n"); foreach ($picks as $file) { echo $file."\n"; exec("exiftool -overwrite_original -Label='crop' ".$file); } echo "\n"; ask("Press return to open Bridge."); exec("open ".args("dir")." -b com.adobe.bridge7"); ask("Press return when crops have been applied."); foreach ($picks as $file) { $parts = chop(shell_exec("exiftool -s -s -s -XMP-crs:CropTop -XMP-crs:CropRight -XMP-crs:CropLeft -XMP-crs:CropBottom ".$file." 2>&1")); @list($top, $right, $left, $bottom) = explode("\n", $parts); if ($top && $right && $left && $bottom) { $key = filter_var(substr(basename($file), 4), FILTER_SANITIZE_NUMBER_INT); $crops[$key]['top'] = $top; $crops[$key]['right'] = $right; $crops[$key]['left'] = $left; $crops[$key]['bottom'] = $bottom; } else { msg("Problem reading crops from file ".$file.". Continue?",2); } } echo "Crop areas have been detected as follows. Press return to write to files.\n"; foreach ($crops as $key => $parts) { echo "\n"; echo $key."\t\tTop: ".bashcolor($parts['top'], $colors[array_search($key,array_keys($crops))]); echo "\tRight: ".bashcolor($parts['right'], $colors[array_search($key,array_keys($crops))]); echo "\tLeft: ".bashcolor($parts['left'], $colors[array_search($key,array_keys($crops))]); echo "\tBottom: ".bashcolor($parts['bottom'], $colors[array_search($key,array_keys($crops))]); } ask(""); echo "Crop Mapping:\n"; foreach ($files as $file) { $key = filter_var(substr(basename($file), 4), FILTER_SANITIZE_NUMBER_INT); $closekey = getClosest($key, array_keys($crops)); $parts = $crops[$closekey]; echo "\n"; echo $file."\t\tTop: ".bashcolor($parts['top'], $colors[array_search($closekey,array_keys($crops))]); echo "\tRight: ".bashcolor($parts['right'], $colors[array_search($closekey,array_keys($crops))]); echo "\tLeft: ".bashcolor($parts['left'], $colors[array_search($closekey,array_keys($crops))]); echo "\tBottom: ".bashcolor($parts['bottom'], $colors[array_search($closekey,array_keys($crops))]); exec("exiftool -overwrite_original -XMP-crs:HasCrop=1 -XMP-crs:AlreadyApplied=0 -XMP-crs:CropTop=".$parts['top']." -XMP-crs:CropRight=".$parts['right']." -XMP-crs:CropLeft=".$parts['left']." -XMP-crs:CropBottom=".$parts['bottom']." ".$file); } echo "\n"; fin(); //////////////////////////////////////////////////////////////////////////////////////////////// // Note: Dupes ////////////////////// } elseif (args("app") == "dupes") { echo Welcome("Find duplicate images"); ///////////////////////////////////////////////////////////////////////// if (args("threshold")) { $threshold = args("threshold"); } else { $threshold = 10; } if (args("walk") > 1) { $walk = args("walk"); } elseif (args("walk")) { msg("Walk must be 2 or greater",1); } else { $walk = 5; } $files = glob(args("dir")."*.*"); echo "Checking thumbnails: "; foreach ($files as $file) { $ext = pathinfo($file,PATHINFO_EXTENSION); if (strtolower($ext) == "dng") { $file = getjpg($file); } $tnfile = "scratch/".basename($file,".".pathinfo($file,PATHINFO_EXTENSION))."_thumb.".pathinfo($file, PATHINFO_EXTENSION); if (!file_exists($tnfile)) { echo "."; exec("vipsthumbnail ".$file." --size 300x300 -o ../".$tnfile." 2>&1"); } else { echo "o"; } $tnfiles[] = $tnfile; } echo "\n\n"; echo "Comparing phash values: "; foreach ($tnfiles as $file) { foreach ($tnfiles as $nfile) { $anum = filter_var($file, FILTER_SANITIZE_NUMBER_INT); $bnum = filter_var($nfile, FILTER_SANITIZE_NUMBER_INT); $diff = abs($anum-$bnum); $done[$file][$nfile] = 1; if ($file != $nfile && $diff < $walk && isset($done[$nfile][$file])) { echo "."; $distance = shell_exec("compare -metric phash ".$file." ".$nfile." /tmp/diffimage 2>&1"); if ($distance < $threshold) { $match[] = array($file, $nfile, $distance); } } } } echo "\n\n"; foreach ($match as $pair) { echo $pair[0]." <> ".$pair[1]." = ".$pair[2]."\n"; } echo "\n"; msg("Review duplicates?",2); foreach ($match as $pair) { $afile = args("dir").basename(str_replace("_thumb", "", $pair[1])); $bfile = args("dir").basename(str_replace("_thumb", "", $pair[0])); if (strtolower($ext) == "dng") { $afile = getdng($afile); $bfile = getdng($bfile); } ask("Press return to compare ".$afile." to ".$bfile); @exec("open ".$afile." ".$bfile." -b com.apple.Preview"); } fin(); //////////////////////////////////////////////////////////////////////////////////////////////// // Note: Resort ////////////////////// } elseif (args("app") == "resort") { echo Welcome("Insert image into numbered sequence"); ///////////////////////////////////////////////////////////////////////// $files = glob(args("dir")."[0-9][0-9][0-9]-*.jpg"); $count = count($files); if (!$count) { msg("Could not find any numbered files in source directory",1); } if (!args("x")) { msg("Please specify a position for inserted file",1); } else { $x = sprintf('%03d',args("x")); } if (!file_exists(args("file"))) { msg("Error reading target file",1); } else { $target = args("file"); } foreach ($files as $file) { $seq[substr(basename($file),0,3)] = $file; } if (!isset($seq[$x])) { msg("Specified position does not exist: ".$x,1); } foreach ($seq as $num => $file) { if ($num == $x) { $ops[] = array($target, args("dir").$x."-".basename($target)); } if ($num >= $x) { $ops[] = array($file, args("dir").sprintf('%03d',$num+1).substr(basename($file),3)); } } foreach ($ops as $parts) { if (file_exists($parts[1])) { msg("Meltdown! Renamed file would overwrite ".$parts[1],1); } } foreach ($ops as $parts) { echo "Renaming ".$parts[0]." to ".$parts[1]."\n"; rename($parts[0], $parts[1]); } fin("Starting file count: ".$count."; Ending file count: ".count(glob(args("dir")."[0-9][0-9][0-9]-*.jpg"))); //////////////////////////////////////////////////////////////////////////////////////////////// // Note: Review ////////////////////// } elseif (args("app") == "review") { echo Welcome("List image dimension statistics"); ///////////////////////////////////////////////////////////////////////// $files = glob(args("dir")."*.*"); foreach ($files as $file) { if (isset($attr)) { $prev_attr = $attr; $attr = null; } $ext = pathinfo($file, PATHINFO_EXTENSION); if ($ext == "png") { $tags = @shell_exec("exiftool -FileSize -ProfileDescription -BitsPerSample -PhotoshopQuality -ImageWidth -ImageHeight -PixelsPerUnitX -PixelsPerUnitY ".$file); } else { $tags = @shell_exec("exiftool -FileSize -ProfileDescription -BitsPerSample -PhotoshopQuality -ImageWidth -ImageHeight -Xresolution -Yresolution ".$file); } foreach (explode("\n", trim($tags)) as $tag) { $parts = explode(":", $tag); $attr[trim($parts[0])] = trim($parts[1]); } if ($ext == "png" && isset($attr['Pixels Per Unit X'])) { $attr['X Resolution'] = round($attr['Pixels Per Unit X']/39.37007874016); $attr['Y Resolution'] = round($attr['Pixels Per Unit Y']/39.37007874016); } $hilite["jpg"] = "blue"; $hilite["png"] = "green"; $hilite["JPG"] = "light blue"; $hilite["tif"] = "dark grey"; $hilite["DNG"] = "purple"; if (!isset($hilite[pathinfo($file, PATHINFO_EXTENSION)])) { echo "Can't read ".$file."\n"; continue; } $print[] = bashcolor($file, $hilite[pathinfo($file, PATHINFO_EXTENSION)]); $size = $attr['File Size']; if ($ext != "DNG") { $quality = @exec("identify -format '%Q' ".$file); } if (isset($quality)) { $size .= " [Q=".$quality."]"; } if (isset($attr['Photoshop Quality'])) { $size .= " [PQ=".$attr['Photoshop Quality']."]"; } $print[] = $size; if (isset($attr['Profile Description'])) { $print[] = bashcolor($attr['Profile Description']." (".$attr['Bits Per Sample']." bit)", "cyan"); } else { $print[] = bashcolor("unknown","black"); } $dims = $attr['Image Width']."x".$attr['Image Height']; $color = "purple"; if (isset($prev_attr['Image Width']) && isset($prev_attr['Image Height'])) { $prev_dims = $prev_attr['Image Width']."x".$prev_attr['Image Height']; if ($dims != $prev_dims) { $color = "light purple"; } } $print[] = bashcolor($attr['Image Width']."x".$attr['Image Height'], $color); if (isset($attr['X Resolution'])) { $print[] = bashcolor(floor($attr['X Resolution']).":".floor($attr['Y Resolution']), "brown"); $width = number_format($attr['Image Width']/$attr['X Resolution'],2); $height = number_format($attr['Image Height']/$attr['Y Resolution'],2); $print[] = bashcolor($width."\" x ".$height."\"", "white", "black"); } else { $print[] = bashcolor("unknown","black"); $print[] = bashcolor("can't determine","black","white"); } foreach ($print as $piece) { if ($piece == $print[0]) { echo str_pad($piece, 60); } else { echo str_pad($piece, 40); } } echo "\n"; $print = null; } fin(); //////////////////////////////////////////////////////////////////////////////////////////////// // Note: Makepdf ////////////////////// } elseif (args("app") == "makepdf") { echo Welcome("Combine finished images into a pdf with python img2pdf"); ///////////////////////////////////////////////////////////////////////// $input = args("dir")."*.*"; $dest = basename(getcwd()).".pdf"; if (file_exists($dest)) { $dest = basename(getcwd())."_".rand(1000,9999).".pdf"; if (file_exists($dest)) { msg("Freak accident",1); } } echo "Creating pdf...\n\n"; exec("img2pdf --verbose --viewer-page-layout twocolumnright --output ".$dest." ".$input); echo "\n"; msg("Press return to open in Acrobat",2); exec("open ".$dest." -b com.adobe.Acrobat.Pro"); fin(); //////////////////////////////////////////////////////////////////////////////////////////////// // Note: Divide // We assume a .JPG file is an unmodified DCIM image and convert to a greyscale tif for overlay. // A .jpg file is treated like a prepared brightness map file and is unmodified. ////////////////////// } elseif (args("app") == "divide") { echo Welcome("Composite image from brightness map"); ///////////////////////////////////////////////////////////////////////// $files = glob(args("dir")."*.{jpg,JPG,tif}", GLOB_BRACE); if (!args("map")) { msg("No brightness map specified",1); } elseif (!file_exists(args("map"))) { msg("Can't open brightness map",1); } elseif (substr(args("map"), -3, 3) == "JPG") { //$method = "auto-level"; $method = "normalize"; $map = "scratch/".basename(args("map"),".JPG")."-divide_map.tif"; if (!file_exists($map)) { @exec("convert -".$method." -colorspace gray ".args("map")." ".$map); } } elseif (substr(args("map"), -3, 3) == "jpg") { $map = args("map"); } $dest = rtrim(args("dir"), '/')."_divided"; if (count(glob($dest."/*.*"))) { msg("Files already exist in destination ".$dest,1); } elseif (!is_dir($dest)) { mkdir($dest); } if (args("q")) { $quality = args("q"); } else { $quality = 95; } $thread = array(); foreach ($files as $file) { $msg = "Dividing ".$file." with ".$map.", Q=".$quality; list ($width, $height) = getimagesize($map); list ($twidth, $theight) = getimagesize($file); if ($width != $twidth | $height != $theight) { $tmap = $map."'[".$twidth."x".$theight."!]'"; $msg .= " (resize map) "; } else { $tmap = $map; } if (args("adjust")) { $msg .= " (".args("adjust").")"; $cmd = "convert ".$file." ".$tmap." -compose Divide_Src -composite -level ".args("adjust")." -quality ".$quality." ".$dest."/".basename($file); } else { $cmd = "convert ".$file." ".$tmap." -compose Divide_Src -composite -quality ".$quality." ".$dest."/".basename($file); } $thread[] = array($msg, $cmd); } msg("Beginning multithreaded convert with ".$xt." threads"); multiexec($thread,$xt); fin(); //////////////////////////////////////////////////////////////////////////////////////////////// // Note: SetDPI ////////////////////// } elseif (args("app") == "setdpi") { echo Welcome("Batch set EXIF resolution tags"); ///////////////////////////////////////////////////////////////////////// $files = glob(args("dir")."*.*"); if (args("height")) { $height = args("height"); } elseif (args("x")) { $x = args("x"); } else { msg("No resolution value",1); } foreach ($files as $file) { echo "Processing ".$file.": "; $ext = pathinfo($file, PATHINFO_EXTENSION); if (isset($height)) { list ($imwidth, $imheight) = getimagesize($file); $x = floor($imheight/$height); } if ($ext == "jpg" | $ext == "JPG") { exec("exiftool -overwrite_original -Xresolution=".$x." -Yresolution=".$x." -jfif:Xresolution=".$x." -jfif:Yresolution=".$x." ".$file); echo "set DPI to ".$x; } elseif ($ext == "DNG") { exec("exiftool -overwrite_original -Xresolution=".$x." -Yresolution=".$x." -SubIFD:Xresolution=".$x." -SubIFD:Yresolution=".$x." -SubIFD1:Xresolution=".$x." -SubIFD1:Yresolution=".$x." ".$file); echo "set DPI to ".$x; } elseif ($ext == "png") { $res = $x*39.37007874016; exec("exiftool -overwrite_original -PixelsPerUnitX=".$res." -PixelsPerUnitY=".$res." ".$file); echo "set DPI to ".$res; } else { echo " -> skip"; } echo "\n"; } fin(); //////////////////////////////////////////////////////////////////////////////////////////////// // Note: Strip ////////////////////// } elseif (args("app") == "strip") { echo Welcome("Strip crop values from images"); ///////////////////////////////////////////////////////////////////////// $files = glob(args("dir")."*.*"); foreach ($files as $file) { echo "Processing ".$file.": "; $ext = pathinfo($file, PATHINFO_EXTENSION); if ($ext == "jpg" | $ext == "JPG" | $ext == "DNG") { $parts = chop(shell_exec("exiftool -s -s -s -XMP-crs:CropTop -XMP-crs:CropRight -XMP-crs:CropLeft -XMP-crs:CropBottom ".$file." 2>&1")); if (strlen($parts) > 1) { list($top, $right, $left, $bottom) = explode("\n", $parts); } if (isset($top) | isset($right) | isset($left) | isset($bottom)) { shell_exec("exiftool -overwrite_original -XMP-crs:CropTop= -XMP-crs:CropRight= -XMP-crs:CropLeft= -XMP-crs:CropBottom= ".$file." 2>&1"); echo "removed"; } else { echo "no crop found"; } } else { echo "cant handle ".$ext; } echo "\n"; } fin(); //////////////////////////////////////////////////////////////////////////////////////////////// // Note: Deskew ////////////////////// } elseif (args("app") == "deskew") { echo Welcome("Detect skew angle and apply to EXIF tags"); ///////////////////////////////////////////////////////////////////////// if (args("max")) { $deskew_max_angle = args("max"); } else { $deskew_max_angle = .4; } if (null !== args("pad")) { $deskew_padding = args("pad"); echo "pad = ".args("pad"); } else { $deskew_padding = 80; } if (null !== args("contrast")) { $deskew_contrast = args("contrast"); } else { $deskew_contrast = 20; } if (args("size")) { $deskew_size = args("size"); } else { $deskew_size = 2200; } die; $files = glob(args("dir")."*.{jpg,JPG,dng,DNG}", GLOB_BRACE); echo "Scanning for crop values: "; if (!args("nomap")) { foreach ($files as $file) { $ext = pathinfo($file,PATHINFO_EXTENSION); $dmfile = "scratch/".basename($file, ".".$ext)."_dmap.jpg"; if (!file_exists($dmfile)) { $parts = chop(shell_exec("exiftool -s -s -s -XMP-crs:CropTop -XMP-crs:CropRight -XMP-crs:CropLeft -XMP-crs:CropBottom -ImageWidth -ImageHeight ".$file." 2>&1")); @list($top, $right, $left, $bottom, $check_width, $check_height) = explode("\n", $parts); if (!$top | !$right | !$left | !$bottom) { msg($file." missing crop information!",1); } $jpeg_width = $check_height; $jpeg_height = $check_width; $top_pixels = floor( $jpeg_width * $top ); $right_pixels = $jpeg_height - floor( $right * $jpeg_height ); $left_pixels = floor( $jpeg_height * $left ); $bottom_pixels = $jpeg_width - floor( $bottom * $jpeg_width ); $width = $jpeg_width - ( $top_pixels + $bottom_pixels ); $height = $jpeg_height - ( $left_pixels + $right_pixels ); $area_left = ( $width + $deskew_padding*2 )."x".( $height + $deskew_padding*2 )."+".( $bottom_pixels - $deskew_padding )."+".( $left_pixels - $deskew_padding ); $area_right = ( $width + $deskew_padding*2 )."x".( $height + $deskew_padding*2 )."+".( $top_pixels - $deskew_padding )."+".( $right_pixels - $deskew_padding ); //echo "\n\n(Area left = ".$area_left.", Area right = ".$area_right.")"; $size = $deskew_size."x".$deskew_size; if (substr(basename($file), 0, 3) % 2 == 0) { $cmd = "convert ".$file." -auto-orient -crop ".$area_right." -level ".$deskew_contrast."%,".(100-$deskew_contrast)."% -colorspace Gray -resize ".$size." ".$dmfile; } else { $cmd = "convert ".$file." -auto-orient -crop ".$area_left." -level ".$deskew_contrast."%,".(100-$deskew_contrast)."% -colorspace Gray -resize ".$size." ".$dmfile; } $msg = "Creating dmap for ".$file; $thread[] = array($msg, $cmd); echo "."; } else { $thread[] = array("Dmap for ".$file." already exists", "true"); echo "o"; } } echo "\n\n"; if (is_array($thread)) { msg("Creating dmaps with ".$xt." threads"); multiexec($thread,$xt); } } else { msg("Skipping dmap creation"); } $thread = null; echo "\n"; msg("Detecting skew..."); foreach ($files as $file) { if (args("nomap")) { $dmfile = $file; } else { $ext = pathinfo($file,PATHINFO_EXTENSION); $dmfile = "scratch/".basename($file, ".".$ext)."_dmap.jpg"; } $result = shell_exec("deskew -o /tmp/null -l 99 \"".$dmfile."\" 2>&1"); $arr = explode("Skew angle found: ", $result); $angle = substr($arr[1], 0, 4); $msg = $file.": ".$angle; if ($angle > $deskew_max_angle | $angle < ($deskew_max_angle*-1)) { $msg .= " (too big)"; } else { $angles[$file] = $angle; } msg($msg); } echo "\n\n"; echo "Checking for existing rotation: "; foreach ($files as $file) { if(shell_exec("exiftool -s -s -s -CropAngle ".$file." 2>&1")) { $msg = $file." already has a CropAngle"; $cmd = "true"; echo "o"; } else { if (isset($angles[$file])) { $msg = "Writing CropAngle ".$angles[$file]." to ".$file; $cmd = "exiftool -overwrite_original -XMP-crs:CropAngle=".$angles[$file]." ".$file; echo "."; } else { $msg = "No CropAngle for ".$file.", writing Rating=1"; $cmd = "exiftool -overwrite_original -Rating=1 ".$file; echo ","; } } $thread[] = array($msg, $cmd); } echo "\n\n"; if (is_array($thread)) { msg("Writing EXIF tags with ".$xt." threads"); //multiexec($thread,$xt); } //////////////////////////////////////////////////////////////////////////////////////////////// // Elif ////////////////////// } else { echo Welcome("Program name \"".$argv[1]."\" not found"); } ///////////////////////////////////////////////////////////////////////// die; // check bridge labels // for i in *; do exiftool -s -s -s -Label $i; done // convert to sRGB lossy // jpegicc -q100 001-Plustek0001.jpg 001-Plustek0001_out.jpg ?>