/dev/null | grep 'Total Number of Cores' | cut -d: -f2 | tr -d ' '")); if (in_array($cores,$reasonable_cores)) { $xt = $cores; } else { $xt = 2; } // Functions function levels_ps_to_imgk($string) { $parts = explode(",", $string); if (count($parts) != 3) { msg("Invalid levels string",1); } else { $black = ($parts[0]/253)."%"; $white = (number_format(($parts[1]/255),5)*100)."%"; $gamma = $parts[2]; return $black.",".$white.",".$gamma; } } function multiexec($thread, $x, $null = true) { global $debug; $batch = array_chunk($thread,$x); foreach ($batch as $group) { $msg = array(); $cmd = array(); foreach ($group as $parts) { $msg[] = $parts[0]; $cmd[] = $parts[1]; } if (strlen($msg[0]) > 1) { $echo = implode("\n", $msg)."\n"; } else { $echo = implode("", $msg); } if ($null) { $exec = implode(" > /dev/null & ",$cmd)." & wait"; } else { $exec = implode(" & ",$cmd)." & wait"; } echo $echo; if ($debug) { echo "\n\n".$exec."\n\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\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 ($query == "levels" && isset($opt['levels'])) { return levels_ps_to_imgk($opt['levels']); } 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: build combine images into a pdf with img2pdf clean remove scratch files crop define EXIF crop values using template files -crops= (6) specify how many files to use desort remove image sequence prefix deskew detect rotation angles -max= (.4) angles greater than this value are ignored -pad= (80) pixels to pad around crop area -contrast= (20) levels attenuation -size= (2200) dmap size in pixels -nomap don't use dmaps depaper divide page with itself using mask (better despine) -mask= mask file for spine-left image -mpush= levels for divide map (ex. \"0-253,0-255,0-2\") -mix=<0-100> percentage mix with original (90% = mostly og) despine remove shadow from spine -levels= levels for overlay (ex. \"0-253,0-255,0-2\") -width= (300) spine width in pixels -q=<0-100> (auto) jpeg quality, default read from source or 95 divide wrapper for imagemagick Divide_Src -map= specify brightness file -mpush= levels for map (ex. \"0-253,0-255,0-2\") or \"auto\" -blur= apply in-place blur to map (slow/dumb) -levels= levels adjustment for resulting image (ex. \"0-253,0-255,0-2\") -q= (95) quality out of 100 -pages map file is for LR rotated pages -mix=<0-100> percentage mix with original (90% = mostly og) dupes Find duplicate images using computed PHASH on thumbnails -threshold= (10) match threshold -walk= (5) comparison scope (compare n image to n, n+1, n+2, etc) fixpdf de-rotate all pages in a pdf -file= pdf file in target dir generate create final jpg images for pdf creation -pixels= scale to x pixels on longest side -inches= set dpi to x inches on longest side -q=<0-100> (90) jpeg quality -levels= photoshop levels adjustment (ex. \"0-253,0-255,0-2\") -px= png size multiplier -plevels= png levels adjustment (otherwise calculated from adjust) match move files and set crop values to match source dir -source= read FROM dir 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= (90) rotation angle -q=<0-100> (auto) 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= (auto) specify midpoint (cover image) strip strip exif crop values from images with exiftool "; echo $help; fin(); //////////////////////////////////////////////////////////////////////////////////////////////// // Note: Brightness_table ////////////////////// } elseif (args("app") == "brightness_table") { echo Welcome("Display brightness values for a directory of images"); ///////////////////////////////////////////////////////////////////////// $files = glob(args("dir")."*.*"); foreach ($files as $file) { echo $file.": ".filter_var(shell_exec("convert ".$file." -colorspace Gray -format \"%[fx:quantumrange*image.mean]\" info:"),FILTER_SANITIZE_NUMBER_INT)."\n"; } fin(); //////////////////////////////////////////////////////////////////////////////////////////////// // Note: Depaper ////////////////////// } elseif (args("app") == "depaper") { echo Welcome("Divide with self using mask"); ///////////////////////////////////////////////////////////////////////// $dest = rtrim(args("dir"), '/')."_depaper"; if (!file_exists(args("mask"))) { msg("Can't open mask",1); } if (count(glob($dest."/*.*"))) { msg("Files already exist in ".$dest.". Move to trash?",2); exec("/opt/local/bin/trash -a ".$dest); mkdir($dest); } elseif (!is_dir($dest)) { mkdir($dest); } $files = glob(args("dir")."*.*"); foreach ($files as $file) { $ext = pathinfo($file,PATHINFO_EXTENSION); $result = $dest."/".basename($file); list ($width, $height) = getimagesize($file); if (substr(basename($file), 0, 3) % 2 == 0) { $cmd = "convert test.jpg \( ".$file." \( ".args("mask")."[".$width."x".$height."] -rotate 180 \) -alpha Off -compose CopyOpacity -composite \) -compose Over -composite ".$result; } else { $cmd = "convert test.jpg \( ".$file." ".args("mask")."[".$width."x".$height."] -alpha Off -compose CopyOpacity -composite \) -compose Over -composite ".$result; } $msg = "Converting ".$file; $thread[] = array($msg, $cmd); } msg("Beginning multithreaded convert with ".$xt." threads"); multiexec($thread,$xt); fin(); //////////////////////////////////////////////////////////////////////////////////////////////// // Note: Despine ////////////////////// } elseif (args("app") == "despine") { echo Welcome("Remove spine on white pages"); ///////////////////////////////////////////////////////////////////////// $threshold = 190; $default_quality = 95; $dest = rtrim(args("dir"), '/')."_despined"; if (count(glob($dest."/*.*"))) { msg("Files already exist in ".$dest.". Move to trash?",2); exec("/opt/local/bin/trash -a ".$dest); mkdir($dest); } elseif (!is_dir($dest)) { mkdir($dest); } if (args("width")) { $swidth = args("width"); } else { $swidth = 240; } if (args("levels")) { $adjust = args("levels"); } else { $adjust = levels_ps_to_imgk("0,250,1"); } $files = glob(args("dir")."[0-9][0-9][0-9]-*"); msg("Comparing spines"); foreach ($files as $file) { $ext = pathinfo($file,PATHINFO_EXTENSION); list ($width, $height) = getimagesize($file); if (substr(basename($file), 0, 3) % 2 == 0) { $geo = "+".($width-$swidth)."+0"; $cropsize = $swidth."x".$height; $crop = $cropsize.$geo; } else { $geo = "+0+0"; $cropsize = $swidth."x".$height; $crop = $cropsize.$geo; } if (args("q")) { $quality = args("q"); } elseif (strtolower($ext) == "jpg") { $quality = chop(shell_exec("identify -format '%Q' ".$file)); } else { $quality = $default_quality; } $msg = $file." matched, despining..."; $cmd = "convert ".$file." \( ".$file." -auto-orient -crop ".$crop." -colorspace gray -level ".$adjust." \) -auto-orient -geometry ".$geo." -compose Divide_Src -composite -quality ".$quality." ".$dest."/".basename($file); if (args("mix")) { $pass1 = str_replace("convert", "convert \(", $cmd); $pass2 = str_replace("-composite", "-composite \) ".$file." -auto-orient -compose dissolve -define compose:args=".args("mix")." -composite", $pass1); $cmd = $pass2; } $thread[] = array($msg, $cmd); } echo "\n\n"; msg("Beginning multithreaded convert with ".$xt." threads"); multiexec($thread,$xt); fin(); //////////////////////////////////////////////////////////////////////////////////////////////// // Note: Match ////////////////////// } elseif (args("app") == "match") { echo Welcome("Match directories"); ///////////////////////////////////////////////////////////////////////// $source = chop(args("source"), "/")."/"; $files = glob($source."[0-9][0-9][0-9]-*"); if (!$files) { msg("No valid files in source dir!",1); } $i = 0; $ix = 0; foreach ($files as $file) { $ext = pathinfo($file,PATHINFO_EXTENSION); $key = substr(basename($file), 0, 3); $match = glob(args("dir")."*".substr(basename($file, ".".$ext), 4)."*"); if (isset($match[0]) && file_exists($match[0])) { $dest = $match[0]; $dparts = pathinfo($dest); if (basename($file,$ext) != basename($dest,$dparts['extension'])) { $newdest = $dparts['dirname']."/".$key."-".$dparts['basename']; msg("Moving ".$dest." to ".$newdest); rename($dest,$newdest); $i++; } else { $newdest = $dest; } $parts = chop(shell_exec("exiftool -s -s -s -XMP-crs:CropTop -XMP-crs:CropRight -XMP-crs:CropLeft -XMP-crs:CropBottom -XMP-crs:CropAngle ".$file." 2>&1")); @list($top, $right, $left, $bottom, $angle) = explode("\n", $parts); if (isset($top) || isset($right) || isset($left) || isset($bottom) || isset($angle)) { $info = "Writing crop information from ".$file." to ".$newdest; $info .= " Top: ".bashcolor($top,"green"); $info .= " Right: ".bashcolor($right,"green"); $info .= " Left: ".bashcolor($left,"green"); $info .= " Bottom: ".bashcolor($bottom,"green"); $info .= " Angle: ".bashcolor($angle,"green"); msg($info); exec("exiftool -overwrite_original -XMP-crs:HasCrop=1 -XMP-crs:AlreadyApplied=0 -XMP-crs:CropTop=".$top." -XMP-crs:CropRight=".$right." -XMP-crs:CropLeft=".$left." -XMP-crs:CropBottom=".$bottom." -XMP-crs:CropAngle=".$angle." ".$newdest); $ix++; } else { msg("Source file ".$file." does not contain crop information"); } } else { msg("No match found for ".$file." in destination"); } } echo "\n"; msg("Moved ".$i." files"); msg("Tagged ".$ix." files"); fin(); //////////////////////////////////////////////////////////////////////////////////////////////// // Note: Generate ////////////////////// } elseif (args("app") == "generate") { echo Welcome("Generate final jpg images"); ///////////////////////////////////////////////////////////////////////// $dest = rtrim(args("dir"), '/')."_generated"; if (count(glob($dest."/*.*"))) { msg("Files already exist in ".$dest.". Move to trash?",2); exec("/opt/local/bin/trash -a ".$dest); mkdir($dest); } elseif (!is_dir($dest)) { mkdir($dest); } if (args("pixels")) { $pixels = args("pixels"); } else { msg("No pixel value specified",1); } if (args("inches")) { $inches = args("inches"); } else { msg("No inches value specified",1); } if (args("q")) { $quality = args("q"); } else { $quality = 90; } if (args("px")) { $multiplier = args("px"); } else { $multiplier = 2; } $files = glob(args("dir")."*.{jpg,JPG}", GLOB_BRACE); echo "Checking EXIF values: "; foreach ($files as $file) { echo "."; $lines = shell_exec("exiftool -s -s -s -f -Label -ConvertToGrayscale -ImageWidth -ImageHeight ".$file); $parts = explode("\n", $lines); $width = $parts[2]; $height = $parts[3]; $ratio = $height/$width; if ($ratio > 3) { $spine[] = array($file,$width,$height); } else { $dims[$file] = $ratio; } if ($parts[0] == "bitmap") { $bitmap[] = array($file,$width,$height); } elseif ($parts[1] == "True") { $grey[] = array($file,$width,$height); } elseif ($ratio < 3) { $color[] = array($file,$width,$height); } } echo "\n\n"; echo "Bitmap files: "; if (isset($bitmap)) { echo count($bitmap); } else { echo "None"; } echo "\n"; echo "Greyscale files: "; if (isset($grey)) { echo count($grey); } else { echo "None"; } echo "\n"; echo "Color files: "; if (isset($color)) { echo count($color); } else { echo "None"; } echo "\n"; echo "Spine files: "; if (isset($spine)) { echo count($spine); } else { echo "None"; } echo "\n\n"; if (count($spine) > 1) { msg("More than one spine file found, meltdown",1); } $ratioavg = number_format(array_sum($dims)/count($dims),3); echo "Average page ratio: ".$ratioavg; if ($ratioavg > 1) { // page is tall $page_height = $pixels; $page_width = floor($page_height/$ratioavg); $paper_height = $inches; $paper_width = number_format($paper_height/$ratioavg,2); } else { $page_width = $pixels; $page_height = floor($page_width*$ratioavg); $paper_width = $inches; $paper_height = number_format($paper_width*$ratioavg,2); } $dpi = number_format($page_height/$paper_height,2); $canonicalpngdpi = number_format($dpi*$multiplier,2); $pngdpi = $canonicalpngdpi*39.37007874016; $dims = $page_width."x".$page_height; $pngdims = ($page_width*$multiplier)."x".($page_height*$multiplier); echo "\n"; echo "Page size: ".$paper_width."\" x ".$paper_height."\""; echo "\n"; echo "Target pixel dimensions: ".$dims." (png=".$pngdims.")"; echo "\n"; echo "DPI = ".$dpi." (png=".$canonicalpngdpi.")"; foreach ($color as $parts) { $jpg[] = $parts; } if (isset($grey)) { foreach ($grey as $parts) { $parts[] = 1; $jpg[] = $parts; } } if (isset($spine)) { $spine[0][] = 2; $jpg[] = $spine[0]; } foreach ($jpg as $parts) { $file = $parts[0]; $output = $dest."/".basename($file); if (isset($parts[3]) && $parts[3] == 1) { $msg = "Processing greyscale page ".$file." @ ".$dims."!"; $cmd = "convert -resize ".$dims."\! -colorspace gray "; } elseif (isset($parts[3]) && $parts[3] == 2) { $sdims = $page_height."x".$page_height; $msg = "Processing spine ".$file." @ ".$sdims; $cmd = "convert -resize ".$sdims." "; } else { $msg = "Processing color page ".$file." @ ".$dims."!"; $cmd = "convert -resize ".$dims."\! "; } if (args("levels")) { $msg .= " (".args("levels").")"; $cmd .= "-level ".args("levels")." "; } $msg .= " [Q=".$quality."]"; $cmd .= "-quality ".$quality." ".$file." ".$output; $thread_a[] = array($msg, $cmd); $thread_b[] = array(".","exiftool -overwrite_original -Xresolution=".$dpi." -Yresolution=".$dpi." -jfif:Xresolution=".$dpi." -jfif:Yresolution=".$dpi." ".$output); } echo "\n\n"; msg("Beginning multithreaded convert with ".$xt." threads"); multiexec($thread_a,$xt); echo "\n"; msg("Setting DPI: "); multiexec($thread_b,$xt); if (isset($bitmap)) { foreach ($bitmap as $parts) { $file = $parts[0]; $ext = pathinfo($file,PATHINFO_EXTENSION); $output = $dest."/".basename($file,$ext)."png"; $msg = "Processing bitmap page ".$file." @ ".$pngdims."!"; $cmd = "convert -resize ".$pngdims."\! "; if (args("levels")) { $adjust = args("levels"); } else { $adjust = "0%,100%,1"; } $parts = explode(",", $adjust); $black = rtrim($parts[0],"%"); $white = rtrim($parts[1],"%"); $gamma = $parts[2]; if (args("plevels")) { $radjust = args("plevels"); } else { $radjust = ($black+60)."%".",".($white-15)."%".",".($gamma-.7); } $msg .= " (".$radjust.")"; $cmd .= "-level ".$radjust." "; $cmd .= "-monochrome -colors 2 -depth 1 -negate ".$file." ".$output; $thread_c[] = array($msg, $cmd); $thread_d[] = array(".","exiftool -overwrite_original -PixelsPerUnitX=".$pngdpi." -PixelsPerUnitY=".$pngdpi." ".$output); } echo "\n\n"; msg("Beginning multithreaded convert with ".$xt." threads"); multiexec($thread_c,$xt); echo "\n"; msg("Setting DPI: "); multiexec($thread_d,$xt); } 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 { exec("/opt/local/bin/trash -a scratch/"); } fin(); //////////////////////////////////////////////////////////////////////////////////////////////// // Note: Edges ////////////////////// } elseif (args("app") == "edges") { echo Welcome("Edge detection"); ///////////////////////////////////////////////////////////////////////// $dest = rtrim(args("dir"), '/')."_edges"; if (count(glob($dest."/*.*"))) { msg("Files already exist in ".$dest.". Move to trash?",2); exec("/opt/local/bin/trash -a ".$dest); mkdir($dest); } elseif (!is_dir($dest)) { mkdir($dest); } $files = glob(args("dir")."*.*"); $size = "1400"; $canny = "0x1+10%+30%"; $hough = "9x9+200"; foreach ($files as $file) { $ext = pathinfo($file,PATHINFO_EXTENSION); $output = $dest."/".basename($file,$ext)."jpg"; $msg = "Creating hough composite from ".$file; $cmd = "convert ".$file."[".$size."x".$size."] -auto-orient \( +clone -canny ".$canny." -write /tmp/".basename($file)."_canny.png "; $cmd .= "-background none -fill red -stroke red -strokewidth 1 -hough-lines ".$hough." -write /tmp/".basename($file)."_lines.png \) "; $cmd .= "-composite ".$output; $thread[] = array($msg, $cmd); $msg = "Creating MVG file from ".$file; $cmd = "convert /tmp/".basename($file)."_canny.png -hough-lines ".$hough." ".$output.".mvg"; $thread[] = array($msg, $cmd); } msg("Beginning multithreaded convert with ".$xt." threads"); multiexec($thread,$xt); 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 ".$dest.". Move to trash?",2); exec("/opt/local/bin/trash -a ".$dest); mkdir($dest); } 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 = chop(shell_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("~/.iterm2/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: Build ////////////////////// } elseif (args("app") == "build") { 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 --author 'Leaf ".$version."' --output ".$dest." ".$input); echo "\n"; msg("Press return to open in Acrobat",2); exec("open ".$dest." -b com.adobe.Acrobat.Pro"); fin(); //////////////////////////////////////////////////////////////////////////////////////////////// // Note: Fixpdf ////////////////////// } elseif (args("app") == "fixpdf") { echo Welcome("Systematically de-rotate pages in pdf file with pdftk"); ///////////////////////////////////////////////////////////////////////// $file = args("dir").args("file"); if (!file_exists($file)) { msg("Problem reading file ".$file,1); } $info = shell_exec("pdftk ".$file." dump_data"); $chunks = explode("PageMediaBegin", $info); $items = ""; $hits = 0; foreach ($chunks as $page => $chunk) { preg_match("/(PageMediaRotation: )(\d+)(\n)/",$chunk,$matches); if (isset($matches[2]) && $matches[2] > 0) { echo "Page ".$page." is rotated ".$matches[2]."\n"; $hits++; } if ($page) { $items .= $page."north "; } } if (!$hits) { msg("No rotated pages found.",1); } $ext = pathinfo($file, PATHINFO_EXTENSION); $output = basename($file,".".$ext)."_derotated.pdf"; echo "\n"; echo "Writing ".$output; echo "\n"; exec("pdftk ".$file." cat ".$items."output ".$output); echo "\n"; msg("Remove original file?",2); exec("/opt/local/bin/trash -a ".$file."; mv ".$output." ".$file); 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("pages")) { $pages = true; } else { $pages = false; } if (!file_exists(args("map"))) { msg("Can't open brightness map",1); } $ext = pathinfo(args("map"), PATHINFO_EXTENSION); if (strtolower($ext) == "tif" && !args("mpush")) { $map = "scratch/".args("map"); copy(args("map"),$map); } else { $map = "scratch/".basename(args("map"),".".$ext)."-divide_map.tif"; $cmd = "convert -colorspace gray "; if (args("mpush") == "auto") { $cmd .= "-normalize "; } elseif (args("mpush")) { $cmd .= "-level ".levels_ps_to_imgk(args("mpush"))." "; } $cmd .= args("map")." ".$map; msg("Converting map..."); @exec($cmd); } if ($pages) { $ext = pathinfo($map, PATHINFO_EXTENSION); $mapleft = str_replace(".".$ext, "-left.tif", $map); $mapright = str_replace(".".$ext, "-right.tif", $map); msg("Making map pages..."); @exec("convert -rotate 90 -flip ".$map." ".$mapleft." 2>&1"); @exec("convert -rotate 270 ".$map." ".$mapright." 2>&1"); } $dest = rtrim(args("dir"), '/')."_divided"; if (count(glob($dest."/*.*"))) { msg("Files already exist in ".$dest.". Move to trash?",2); exec("/opt/local/bin/trash -a ".$dest); mkdir($dest); } elseif (!is_dir($dest)) { mkdir($dest); } if (args("q")) { $quality = args("q"); } else { $quality = 95; } echo "Building threads: "; $thread = array(); foreach ($files as $file) { $msg = "Dividing ".$file." with ".$map.", Q=".$quality; $cmd = "convert ".$file; $output = $dest."/".basename($file); if ($pages) { $label = trim(shell_exec("exiftool -s -s -s -Label ".$file)); if ($label == "non-page") { $msg .= " "; $cmd = "cp ".$file." ".$output; echo "o"; $thread[] = array($msg, $cmd); continue; } if (substr(basename($file), 0, 3) % 2 == 0) { $use = $mapright; } else { $use = $mapleft; } list ($width, $height) = getimagesize($use); list ($twidth, $theight) = getimagesize($file); if ($width != $twidth || $height != $theight) { $tmap = $use."'[".$twidth."x".$theight."!]'"; $msg .= " (resize map)"; } else { $tmap = $use; } } else { $tmap = $map; } if (args("blur")) { $msg .= " (".args("blur").")"; $overlay = "\( ".$tmap." -blur ".args("blur")." \)"; } else { $overlay = $tmap; } $cmd .= " ".$overlay." -compose Divide_Src -composite -quality ".$quality." "; if (args("levels")) { $msg .= " (".args("levels").") "; $cmd .= "-level ".args("levels")." "; } $cmd .= $output; echo "."; if (args("mix")) { $pass1 = str_replace("convert", "convert \(", $cmd); $pass2 = str_replace("-composite", "-composite \) ".$file." -compose dissolve -define compose:args=".args("mix")." -composite", $pass1); $cmd = $pass2; } $thread[] = array($msg, $cmd); } echo "\n\n"; 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 -XMP-crs:CropAngle ".$file." 2>&1")); if (strlen($parts) > 1) { @list($top, $right, $left, $bottom, $angle) = explode("\n", $parts); } if (isset($top) || isset($right) || isset($left) || isset($bottom) || isset($angle)) { shell_exec("exiftool -overwrite_original -XMP-crs:CropTop= -XMP-crs:CropRight= -XMP-crs:CropLeft= -XMP-crs:CropBottom= -XMP-crs:CropAngle= ".$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"); } 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; } $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"; echo "Deskewing with ".$xt." threads: "; foreach ($files as $file) { $ext = pathinfo($file,PATHINFO_EXTENSION); if (args("nomap")) { $dmfile = $file; } else { $dmfile = "scratch/".basename($file, ".".$ext)."_dmap.jpg"; } $txtfile = "scratch/".basename($file, ".".$ext)."_angle.txt"; if (file_exists($txtfile)) { $msg = "o"; $cmd = "true"; } else { $msg = "."; $cmd = "deskew -o /tmp/null -l 99 ".$dmfile." | grep 'Skew angle found:' | cut -f2- -d: | tr -d ' \n' > ".$txtfile; } $thread[] = array($msg, $cmd); } multiexec($thread,$xt,false); echo "\n\n"; foreach ($files as $file) { $txtfile = "scratch/".basename($file, ".".$ext)."_angle.txt"; $angle = file_get_contents($txtfile); $msg = $file." = ".$angle; if ($angle > $deskew_max_angle || $angle < ($deskew_max_angle*-1)) { $msg .= " (too big)"; } else { $angles[$file] = $angle; } msg($msg); } $thread = null; echo "\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"; msg("Write to EXIF?",2); if (is_array($thread)) { msg("Writing EXIF tags with ".$xt." threads"); multiexec($thread,$xt); } fin(); //////////////////////////////////////////////////////////////////////////////////////////////// // 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 ?>