2091 lines
62 KiB
PHP
2091 lines
62 KiB
PHP
<?php
|
|
|
|
// Leaf - Tools for Book Scans
|
|
// No license, do not distribute
|
|
|
|
$version = "0.9.0.2";
|
|
$time_start = microtime(true);
|
|
date_default_timezone_set("America/Los_Angeles");
|
|
$tmpdir = "/tmp/leaf/".time()."/";
|
|
mkdir($tmpdir,0777,TRUE);
|
|
|
|
// Detect cores
|
|
|
|
$reasonable_cores = array(1,2,3,4,6,8,16);
|
|
$cores = chop(shell_exec("system_profiler SPHardwareDataType 2> /dev/null | grep 'Total Number of Cores' | cut -d: -f2 | tr -d ' '"));
|
|
if (in_array($cores,$reasonable_cores)) {
|
|
$xt = $cores;
|
|
} else {
|
|
$xt = 2;
|
|
}
|
|
|
|
// Simple log
|
|
|
|
$message = date("Y-m-d_H-i-s", time())." (".$version.")\n".implode(" ",$argv);
|
|
file_put_contents("leaf.log",$message,FILE_APPEND);
|
|
|
|
// Functions
|
|
|
|
function crop_imgk_to_xmp($cwidth, $cheight, $xoff, $yoff, $width, $height) {
|
|
|
|
$top = $yoff/$height;
|
|
$right = (($xoff+$cwidth)/$width);
|
|
$left = $xoff/$width;
|
|
$bottom = (($yoff+$cheight)/$height);
|
|
|
|
return array(number_format($top,6), number_format($right,6), number_format($left,6), number_format($bottom,6));
|
|
|
|
}
|
|
|
|
function crop_xmp_to_imgk($parts, $deskew_padding) {
|
|
|
|
@list($top, $right, $left, $bottom, $check_width, $check_height, $orientation) = explode("\n", $parts);
|
|
|
|
if (!$top || !$right || !$left || !$bottom || !$check_width || !$check_height || ! $orientation) {
|
|
return null;
|
|
}
|
|
|
|
$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 );
|
|
|
|
if ($orientation == "Rotate 90 CW") {
|
|
return ( $width + $deskew_padding*2 )."x".( $height + $deskew_padding*2 )."+".( $bottom_pixels - $deskew_padding )."+".( $left_pixels - $deskew_padding );
|
|
} elseif ($orientation == "Rotate 270 CW") {
|
|
return ( $width + $deskew_padding*2 )."x".( $height + $deskew_padding*2 )."+".( $top_pixels - $deskew_padding )."+".( $right_pixels - $deskew_padding );
|
|
} else {
|
|
return null;
|
|
}
|
|
|
|
}
|
|
|
|
function levels_ps_to_imgk($string) {
|
|
$parts = explode(",", $string);
|
|
if (count($parts) != 3) {
|
|
msg("Invalid levels string",1);
|
|
} else {
|
|
$black = (number_format(($parts[0]/253),5)*100)."%";
|
|
$white = (number_format(($parts[2]/255),5)*100)."%";
|
|
$gamma = $parts[1];
|
|
return $black.",".$white.",".$gamma;
|
|
}
|
|
}
|
|
|
|
function olevels_ps_to_imgk($string) {
|
|
$parts = explode(",", $string);
|
|
if (count($parts) != 2) {
|
|
msg("Invalid levels string",1);
|
|
} else {
|
|
$black = (number_format(($parts[0]/253),5)*100)."%";
|
|
$white = (number_format(($parts[1]/255),5)*100)."%";
|
|
return $black.",".$white;
|
|
}
|
|
}
|
|
|
|
function unsharp_ps_to_imgk($string) {
|
|
$parts = explode(",", $string);
|
|
if (count($parts) != 3) {
|
|
msg("Invalid levels string",1);
|
|
} else {
|
|
$radius = "0x".($parts[1]+1);
|
|
$amount = $parts[0]/100;
|
|
$threshold = $parts[2]/255;
|
|
return $radius."+".$amount."+".$threshold;
|
|
}
|
|
}
|
|
|
|
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 ($debug || !$null) {
|
|
$pipe = "";
|
|
} else {
|
|
$pipe = " 2>&1";
|
|
}
|
|
$exec = implode($pipe." & ",$cmd).$pipe." & wait";
|
|
echo $echo;
|
|
if ($debug) {
|
|
echo "\n".str_replace($pipe." & ", "\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;
|
|
}
|
|
$finished = "\nFinished in ".round($time = microtime(true)-$_SERVER["REQUEST_TIME_FLOAT"])." seconds\n\n";
|
|
file_put_contents("leaf.log",$finished,FILE_APPEND);
|
|
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 = strtolower(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 ($query == "plevels" && isset($opt['plevels'])) {
|
|
return levels_ps_to_imgk($opt['plevels']);
|
|
} elseif ($query == "olevels" && isset($opt['olevels'])) {
|
|
return olevels_ps_to_imgk($opt['olevels']);
|
|
} elseif ($query == "unsharp" && isset($opt['unsharp'])) {
|
|
return unsharp_ps_to_imgk($opt['unsharp']);
|
|
} elseif (isset($opt[$query])) {
|
|
return $opt[$query];
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function Welcome($msg) {
|
|
global $version;
|
|
global $debug;
|
|
if ($debug) {
|
|
$out .= bashcolor("Leaf Version: ".$version." debug", "white", "red")." ".shell_exec("date");
|
|
} else {
|
|
$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");
|
|
}
|
|
|
|
if (args("debug")) {
|
|
$debug = 1;
|
|
} else {
|
|
$debug = 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Note: Help
|
|
//////////////////////
|
|
if (!args("app") | args("app") == "help" | args("app") == "-help" | args("app") == "--help") {
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
$help = "Leaf $version
|
|
USAGE: leaf [mode] [-options] directory
|
|
|
|
Modes:
|
|
autocrop crop using row/column brightness method
|
|
-levels=<params> levels adjustment for resulting image (ex. \"0-253,0-2,0-255\")
|
|
-trim=<t,r,l,b> pixels to trim from top, right, left, bottom
|
|
build combine images into a pdf with img2pdf
|
|
clean remove scratch files
|
|
crop define EXIF crop values using template files
|
|
-crops=<num> (6) specify how many files to use
|
|
desort remove image sequence prefix
|
|
deskew detect rotation angles
|
|
-max=<num> (.4) angles greater than this value are ignored
|
|
-pad=<num> (80) pixels to pad around crop area
|
|
-contrast=<num> (20) levels attenuation
|
|
-size=<num> (2200) dmap size in pixels
|
|
-nomap don't use dmaps
|
|
depaper divide page with itself using mask (better despine)
|
|
-mask=<file> mask file for spine-left image
|
|
-mpush=<levels> levels for divide map (ex. \"0-253,0-2,0-255\")
|
|
-mix=<0-100> (null) percentage mix with original (90% = mostly og)
|
|
-skip=<label> (null) skip files with this label
|
|
despine remove shadow from spine
|
|
-levels=<params> levels for overlay (ex. \"0-253,0-2,0-255\")
|
|
-width=<pixels> (300) spine width in pixels
|
|
-q=<0-100> (auto) jpeg quality, default read from source or 95
|
|
divide wrapper for imagemagick Divide_Src
|
|
-map=<file> specify brightness file
|
|
-mpush=<levels> levels for map (ex. \"0-253,0-2,0-255\") or \"auto\"
|
|
-blur=<pixels> apply in-place blur to map (slow/dumb)
|
|
-levels=<params> levels adjustment for resulting image (ex. \"0-253,0-2,0-255\")
|
|
-q=<quality> (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=<num> (10) match threshold
|
|
-walk=<num> (5) comparison scope (compare n image to n, n+1, n+2, etc)
|
|
fixpdf de-rotate all pages in a pdf
|
|
-file=<pdf> pdf file in target dir
|
|
generate create final jpg images for pdf creation
|
|
-pixels=<num> scale to x pixels on longest side
|
|
-inches=<num> set dpi to x inches on longest side
|
|
-q=<0-100> (90) jpeg quality
|
|
-levels=<params> photoshop levels adjustment (ex. \"10,.9,235\")
|
|
-olevels=<params> output levels adjustment (ex. \"0,250\")
|
|
-mix=<num> percentage to mix with original (0-100, 90=mostly original)
|
|
-px=<num> png size multiplier
|
|
-plevels=<params> png levels adjustment (otherwise calculated from adjust)
|
|
-pt=<0-255> use threshold method with photoshop level value
|
|
-pc=<2,2!,3> (2) colors for final png (2! = intermediary dithering)
|
|
-unsharp=<params> photoshop unsharp parameters (ex. \"100,1,0\")
|
|
-skipc skip color images
|
|
-skipg skip grayscale images
|
|
-skipb skip bitmap images
|
|
match move files and set crop values to match source dir
|
|
-source=<dir> read FROM dir
|
|
profile apply xmp profile to images (requires exiv2 > 0.25)
|
|
-file=<file> xmp profile
|
|
resort reorder image sequence by adding a new image
|
|
-file=<file> file to insert
|
|
-x=<num> position of inserted file
|
|
review print a table of image dimension statistics
|
|
rotate batch transform rotate (lossy)
|
|
-x=<angle> (90) rotation angle
|
|
-q=<0-100> (auto) jpeg quality, default read from source or 95
|
|
setdpi set image dpi with exiftool
|
|
-x=<dpi> specify dpi
|
|
-height=<inches> calculate dpi from specified height
|
|
sort sort files as AAABBB -> ABABAB
|
|
-m=<num> (auto) specify midpoint (cover image)
|
|
-prerotated images are already rotated
|
|
strip strip exif crop values from images with exiftool
|
|
|
|
";
|
|
echo $help;
|
|
fin();
|
|
|
|
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 = abs(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);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Note: Autocrop
|
|
//////////////////////
|
|
} elseif (args("app") == "autocrop") {
|
|
echo Welcome("Crop using row/column brightness method");
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
if (args("levels")) {
|
|
$adjust = args("levels");
|
|
}
|
|
|
|
if (args("trim")) {
|
|
$parts = explode(",",args("trim"));
|
|
$top_trim = $parts[0];
|
|
$right_trim = $parts[1];
|
|
$left_trim = $parts[2];
|
|
$bottom_trim = $parts[3];
|
|
}
|
|
|
|
echo "Building crop threads: ";
|
|
|
|
$files = glob(args("dir")."*.*");
|
|
foreach ($files as $file) {
|
|
|
|
if (isset($adjust)) {
|
|
$adjfile = "\( ".$file." -level ".$adjust." \)";
|
|
} else {
|
|
$adjfile = $file;
|
|
}
|
|
|
|
$cmd_a = "convert \( ".$adjfile." +repage -scale x1! -bordercolor black -border 1 -fuzz 30% -trim \) -format \"%w\n%O\" info: > scratch/".basename($file)."_autocropx.txt";
|
|
$cmd_b = "convert \( ".$adjfile." +repage -scale 1x! -bordercolor black -border 1 -fuzz 60% -trim \) -format \"%h\n%O\" info: > scratch/".basename($file)."_autocropy.txt";
|
|
$cmd_c = "exiftool -XMP-crs:CropTop -XMP-crs:CropRight -XMP-crs:CropLeft -XMP-crs:CropBottom -ImageWidth -ImageHeight ".$file." > scratch/".basename($file)."_stats.txt";
|
|
|
|
$thread[] = array(".", $cmd_a);
|
|
$thread[] = array(".", $cmd_b);
|
|
$thread[] = array(".", $cmd_c);
|
|
|
|
}
|
|
|
|
multiexec($thread,$xt,false);
|
|
|
|
echo "\n";
|
|
|
|
foreach ($files as $file) {
|
|
|
|
$tags = file_get_contents("scratch/".basename($file)."_stats.txt");
|
|
foreach (explode("\n", trim($tags)) as $tag) {
|
|
$parts = explode(":", $tag);
|
|
$attr[trim($parts[0])] = trim($parts[1]);
|
|
}
|
|
|
|
$width = $attr['Image Width'];
|
|
$height = $attr['Image Height'];
|
|
|
|
if (isset($attr['Crop Top']) && isset($attr['Crop Right']) && isset($attr['Crop Left']) && isset($attr['Crop Bottom'])) {
|
|
echo "\nExisting crop values for ".$file.": ";
|
|
echo "\tTop: ".bashcolor($attr['Crop Top'], "light blue");
|
|
echo "\tRight: ".bashcolor($attr['Crop Right'], "light green");
|
|
echo "\tLeft: ".bashcolor($attr['Crop Left'], "light cyan");
|
|
echo "\tBottom: ".bashcolor($attr['Crop Bottom'], "light red");
|
|
} else {
|
|
echo "\nNo existing crop values for ".$file;
|
|
}
|
|
|
|
$detectx = explode("\n",file_get_contents("scratch/".basename($file)."_autocropx.txt"));
|
|
$detecty = explode("\n",file_get_contents("scratch/".basename($file)."_autocropy.txt"));
|
|
$detectxparts = explode("+", chop($detectx[1]));
|
|
$detectyparts = explode("+", chop($detecty[1]));
|
|
|
|
if ($debug) { echo "\n".bashcolor("imgk values: cwidth: ".$detectx[0].", cheight: ".$detecty[0].", xoff: ".$detectxparts[1].", yoff: ".$detectxparts[2].", width: ".$width.", height: ".$height, "light purple"); }
|
|
|
|
if (isset($detectx[0]) && $detectx[0] > 1) { $cwidth = $detectx[0]; } else { $cwidth = null; }
|
|
if (isset($detecty[0]) && $detecty[0] > 1) { $cheight = $detecty[0]; } else { $cheight = null; }
|
|
if (isset($detectxparts[1]) && $detectxparts[1] > 1) { $xoff = $detectxparts[1]; } else { $xoff = null; }
|
|
if (isset($detectyparts[2])) { $yoff = $detectyparts[2]; } else { $yoff = null; }
|
|
|
|
if ($cwidth && $cheight && $xoff && $yoff) {
|
|
|
|
list($ntop, $nright, $nleft, $nbottom) = crop_imgk_to_xmp($cwidth, $cheight, $xoff, $yoff, $width, $height);
|
|
|
|
echo "\nNew crop values for ".$file.": ";
|
|
echo "\t\tTop: ".bashcolor($ntop, "blue");
|
|
echo "\tRight: ".bashcolor($nright, "green");
|
|
echo "\tLeft: ".bashcolor($nleft, "cyan");
|
|
echo "\tBottom: ".bashcolor($nbottom, "red");
|
|
|
|
$msg = "Setting crop values for ".$file;
|
|
$cmd = "exiftool -overwrite_original -XMP-crs:HasCrop=1 -XMP-crs:AlreadyApplied=0 -XMP-crs:CropTop=".$ntop." -XMP-crs:CropRight=".$nright." -XMP-crs:CropLeft=".$nleft." -XMP-crs:CropBottom=".$nbottom." ".$file;
|
|
|
|
} else {
|
|
|
|
$msg = "Incomplete crop values for ".$file.", skipping...";
|
|
$cmd = "true";
|
|
|
|
}
|
|
|
|
$threadx[] = array($msg, $cmd);
|
|
|
|
}
|
|
|
|
echo "\n\n";
|
|
|
|
msg("Write crop values?",2);
|
|
multiexec($threadx,$xt);
|
|
|
|
fin();
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Note: Folders
|
|
//////////////////////
|
|
} elseif (args("app") == "folders") {
|
|
echo Welcome("Move digicam images into folders");
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
if (file_exists("tenrecjpg") || file_exists("tenrecdng")) {
|
|
msg("Folder already exists",1);
|
|
}
|
|
|
|
exec("mkdir tenrecdng; mkdir tenrecjpg; mv *.JPG tenrecjpg/; mv *.DNG tenrecdng/");
|
|
|
|
fin();
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Note: Notes
|
|
//////////////////////
|
|
} elseif (args("app") == "notes") {
|
|
echo Welcome("Save shell commands to text file");
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
if (args("lines")) {
|
|
$lines = args("lines");
|
|
} else {
|
|
$lines = 100;
|
|
}
|
|
|
|
$notes = shell_exec("tail -n ".$lines." /Users/peter/.bash_history");
|
|
foreach (explode("\n", $notes) as $line) {
|
|
if (substr($line, 0, 4) == "leaf") {
|
|
$keep[] = $line;
|
|
}
|
|
}
|
|
|
|
if (!isset($keep[0])) {
|
|
msg("Error reading bash history",1);
|
|
} else {
|
|
file_put_contents("leaf_notes.txt", implode("\n", $keep));
|
|
}
|
|
|
|
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.": ".abs(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 (!args("mpush")) {
|
|
msg("Please specify divide levels with mpush",1);
|
|
} else {
|
|
$mpush = levels_ps_to_imgk(args("mpush"));
|
|
}
|
|
|
|
if (count(glob($dest."/*.*"))) {
|
|
msg("Files already exist in ".$dest.". Move to trash?",2);
|
|
exec("/opt/local/bin/trash ".$dest);
|
|
mkdir($dest);
|
|
} elseif (!is_dir($dest)) {
|
|
mkdir($dest);
|
|
}
|
|
|
|
msg("Building threads...");
|
|
|
|
$files = glob(args("dir")."*.*");
|
|
foreach ($files as $file) {
|
|
$ext = pathinfo($file,PATHINFO_EXTENSION);
|
|
$result = $dest."/".basename($file);
|
|
list ($width, $height) = getimagesize($file);
|
|
$divided = "\( ".$file." \( ".$file." -colorspace gray -level ".$mpush." \) -compose Divide_Src -composite \)";
|
|
if (substr(basename($file), 0, 3) % 2 == 0) {
|
|
$cmd = "convert ".$file." \( ".$divided." \( ".args("mask")."[".$width."x".$height."!] -flop \) -alpha Off -compose CopyOpacity -composite \) -compose Over -composite ".$result;
|
|
} else {
|
|
$cmd = "convert ".$file." \( ".$divided." ".args("mask")."[".$width."x".$height."!] -alpha Off -compose CopyOpacity -composite \) -compose Over -composite ".$result;
|
|
}
|
|
if (args("mix")) {
|
|
$pass1 = str_replace("convert", "convert \(", $cmd);
|
|
$pass2 = str_replace("-composite ".$result, "-composite \) ".$file." -auto-orient -compose dissolve -define compose:args=".args("mix")." -composite ".$result, $pass1);
|
|
$cmd = $pass2;
|
|
}
|
|
$msg = "Converting ".$file;
|
|
if (args("skip")) {
|
|
$label = trim(shell_exec("exiftool -s -s -s -Label ".$file));
|
|
if ($label == args("skip")) {
|
|
$msg .= " <Skipping label=".args("skip").">";
|
|
$cmd = "cp ".$file." ".$result;
|
|
}
|
|
}
|
|
$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 ".$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 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 ".$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" || $parts[0] == "grayscale") {
|
|
$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 = round($page_height/$ratioavg);
|
|
$paper_height = $inches;
|
|
$paper_width = number_format($paper_height/$ratioavg,2);
|
|
} else {
|
|
$page_width = $pixels;
|
|
$page_height = round($page_width*$ratioavg);
|
|
$paper_width = $inches;
|
|
$paper_height = number_format($paper_width*$ratioavg,2);
|
|
}
|
|
|
|
$dpi = round($page_height/$paper_height,3);
|
|
$canonicalpngdpi = round($dpi)*$multiplier;
|
|
$pngdpi = round($canonicalpngdpi*39.37007874016);
|
|
$dims = $page_width."x".$page_height;
|
|
// png dpi does not accept decimal points, so we will adjust the png dims slightly to better match jpg size
|
|
$pngdims = (round($canonicalpngdpi*$paper_width))."x".(round($canonicalpngdpi*$paper_height));
|
|
|
|
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=".$pngdpi.")";
|
|
|
|
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);
|
|
$iscolor = false;
|
|
|
|
if (isset($parts[3]) && $parts[3] == 1) {
|
|
|
|
if (args("skipg")) { continue; }
|
|
|
|
$msg = "Processing greyscale page ".$file." @ ".$dims."!";
|
|
// $cmd = "convert -resize ".$dims."\! -colorspace gray -type TrueColor ";
|
|
$cmd = "convert -resize ".$dims."\! -colorspace gray -type GrayScale ";
|
|
|
|
} elseif (isset($parts[3]) && $parts[3] == 2) {
|
|
|
|
$sdims = $page_height."x".$page_height;
|
|
$msg = "Processing spine ".$file." @ ".$sdims;
|
|
$cmd = "convert -resize ".$sdims." ";
|
|
|
|
} else {
|
|
|
|
if (args("skipc")) { continue; }
|
|
|
|
$iscolor = true;
|
|
|
|
$msg = "Processing color page ".$file." @ ".$dims."!";
|
|
$cmd = "convert -resize ".$dims."\! ";
|
|
|
|
}
|
|
|
|
$levelcmd = "";
|
|
|
|
if (args("levels")) {
|
|
$msg .= " (".args("levels").")";
|
|
$levelcmd .= "-level ".args("levels")." ";
|
|
}
|
|
|
|
if (args("olevels")) {
|
|
$msg .= " (".args("olevels").")";
|
|
$levelcmd .= "+level ".args("olevels")." ";
|
|
}
|
|
|
|
if ($iscolor && args("modulate")) {
|
|
$msg .= " (".args("modulate").")";
|
|
$levelcmd .= "-modulate ".args("modulate")." ";
|
|
}
|
|
|
|
if (args("mix") && $levelcmd && $iscolor) {
|
|
// if this is a mix, we reset the $cmd so we can nest the levels command and mix with og
|
|
$msg .= " <MIX=".args("mix").">";
|
|
$cmd = "convert \( ".$file." -resize ".$dims."\! ".$levelcmd."\) \( ".$file." -resize ".$dims."\! \) -define compose:args=".args("mix")." -compose blend -composite ";
|
|
} else {
|
|
$cmd .= $levelcmd;
|
|
}
|
|
|
|
if (args("unsharp")) {
|
|
$msg .= " (".args("unsharp").")";
|
|
$cmd .= "-unsharp ".args("unsharp")." ";
|
|
}
|
|
|
|
$msg .= " [Q=".$quality."]";
|
|
$cmd .= "-quality ".$quality." ";
|
|
|
|
if (args("mix") && $levelcmd && $iscolor) {
|
|
$cmd .= $output;
|
|
} else {
|
|
$cmd .= $file." ".$output;
|
|
}
|
|
|
|
//echo $cmd."\n";
|
|
|
|
$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) && !args("skipb")) {
|
|
|
|
if (!args("pt")) {
|
|
|
|
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-.8);
|
|
}
|
|
|
|
msg("Checking bitmap output for brightness...");
|
|
$testfile = $bitmap[0][0];
|
|
$level = chop(shell_exec("convert \( ".$testfile." -resize ".$pngdims."\! -level ".$radjust." -monochrome -colors 2 -depth 1 \) -colorspace Gray -format \"%[fx:quantumrange*image.mean]\" info:"));
|
|
|
|
if ($level < 20000) {
|
|
msg("Image is dark, applying negate");
|
|
$negate = "-negate ";
|
|
} else {
|
|
msg("Image is bright, skipping negate");
|
|
$negate = "";
|
|
}
|
|
|
|
} else {
|
|
|
|
$threshold = (number_format((args("pt")/255),5)*100)."%";
|
|
|
|
}
|
|
|
|
foreach ($bitmap as $parts) {
|
|
|
|
$file = $parts[0];
|
|
$ext = pathinfo($file,PATHINFO_EXTENSION);
|
|
$output = $dest."/".basename($file,$ext)."png";
|
|
|
|
$msg = "Processing bitmap page ".$file." @ ".$pngdims."!";
|
|
|
|
if (args("pt")) {
|
|
|
|
if (args("pc") == "2!") { // a b&w bitmap image with an intermediary antialiased step (darker text)
|
|
|
|
$msg .= " (".$threshold.", ".args("pc").")";
|
|
$cmd = "convert \( \( \( ".$file." -threshold ".$threshold." \) -set colorspace sRGB -type truecolor \) -resize ".$pngdims."\! \) -threshold 95% ".$output;
|
|
|
|
} elseif (args("pc") == "3") { // a b&w bitmap image with an additional color for antialiasing
|
|
|
|
$msg .= " (".$threshold.", ".args("pc").")";
|
|
$cmd = "convert \( \( \( ".$file." -threshold ".$threshold." \) -set colorspace sRGB -type truecolor \) -resize ".$pngdims."\! \) -set colorspace Grayscale -colors 4 -depth 2 ".$output;
|
|
|
|
} else { // a b&w bitmap image
|
|
|
|
$msg .= " (".$threshold.")";
|
|
$cmd = "convert -resize ".$pngdims."\! -threshold ".$threshold." ".$file." ".$output;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (args("pc") == "2!") { // a b&w bitmap image with an intermediary antialiased step (darker text)
|
|
|
|
$msg .= " (".$radjust.", ".args("pc").")";
|
|
$cmd = "convert \( \( \( ".$file." -level ".$radjust." -monochrome -colors 2 -depth 1 ".$negate."\) -set colorspace sRGB -type truecolor \) -resize ".$pngdims."\! \) -threshold 95% ".$output;
|
|
|
|
} elseif (args("pc") == "3") { // a b&w bitmap image with an additional color for antialiasing
|
|
|
|
$msg .= " (".$radjust.", ".args("pc").")";
|
|
$cmd = "convert \( ".$file." -level ".$radjust." -resize ".$pngdims."\! \) -set colorspace Grayscale -colors 4 -depth 2 ".$output;
|
|
|
|
} else { // a b&w bitmap image
|
|
|
|
$msg .= " (".$radjust.")";
|
|
$cmd = "convert -resize ".$pngdims."\! -level ".$radjust." -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 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 ".$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 ".$tmpdir.basename($file)."_canny.png ";
|
|
$cmd .= "-background none -fill red -stroke red -strokewidth 1 -hough-lines ".$hough." -write ".$tmpdir.basename($file)."_lines.png \) ";
|
|
$cmd .= "-composite ".$output;
|
|
$thread[] = array($msg, $cmd);
|
|
$msg = "Creating MVG file from ".$file;
|
|
$cmd = "convert ".$tmpdir.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 ".$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 = abs(filter_var($mid, FILTER_SANITIZE_NUMBER_INT));
|
|
}
|
|
|
|
$e = 2; $o = 1;
|
|
|
|
if (args("prerotated")) {
|
|
$leftrotation = 1;
|
|
$rightrotation = 3;
|
|
} else {
|
|
$leftrotation = 8;
|
|
$rightrotation = 6;
|
|
}
|
|
|
|
foreach ($files as $file) {
|
|
$num = abs(filter_var($file, FILTER_SANITIZE_NUMBER_INT));
|
|
if ($num < $midpoint_key) {
|
|
$new = args("dir").sprintf('%03d',$e)."-".basename($file);
|
|
$ops[$e] = array($file,$new,$leftrotation);
|
|
$e = $e+2;
|
|
} else {
|
|
$new = args("dir").sprintf('%03d',$o)."-".basename($file);
|
|
$ops[$o] = array($file,$new,$rightrotation);
|
|
$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 = $tmpdir.basename($left[1]); $tmpright = $tmpdir.basename($right[1]);
|
|
if ($ext == "DNG" && getjpg($left[0]) && getjpg($right[0])) {
|
|
$lfile = getjpg($left[0]); $rfile = getjpg($right[0]);
|
|
$tmpleft = $tmpdir.basename($left[1],$ext)."JPG"; $tmpright = $tmpdir.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." ".$tmpdir."contact_sheet_".$key.".jpg");
|
|
$sheets[] = $tmpdir."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 = abs(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.bridge8");
|
|
|
|
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 = abs(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 = abs(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 = abs(filter_var($file, FILTER_SANITIZE_NUMBER_INT));
|
|
$bnum = abs(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." ".$tmpdir."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]-*.*");
|
|
$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]-*.*")));
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// 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(round($attr['X Resolution']).":".round($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)) {
|
|
msg("Files ".$dest." already exists. Move to trash?",2);
|
|
exec("/opt/local/bin/trash ".$dest);
|
|
}
|
|
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 ".$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 ".$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 .= " <Skipping non-page>";
|
|
$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 = round($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 -Orientation ".$file." 2>&1"));
|
|
$getcrop = crop_xmp_to_imgk($parts,$deskew_padding);
|
|
|
|
if (!$getcrop) {
|
|
msg($file." missing crop or orientation information!",1);
|
|
}
|
|
|
|
$size = $deskew_size."x".$deskew_size;
|
|
|
|
if (strtolower($ext) == "dng") {
|
|
$adj = "0%,".(100-$deskew_contrast)."%";
|
|
} else {
|
|
$adj = $deskew_contrast."%,".(100-$deskew_contrast)."%";
|
|
}
|
|
|
|
$cmd = "convert ".$file." -auto-orient -crop ".$getcrop." -level ".$adj." -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;
|
|
|
|
?>
|