This commit is contained in:
2019-05-07 01:54:27 -07:00
parent 659d1ec70d
commit 81dc6c8c1a

242
leaf.php
View File

@@ -1,7 +1,8 @@
<?php
// Leaf - Tools for Book Scans
$version = "0.8.1";
$version = "0.8.3";
$debug = 0;
$time_start = microtime(true);
date_default_timezone_set("America/Los_Angeles");
@@ -17,7 +18,20 @@ if (in_array($cores,$reasonable_cores)) {
// 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();
@@ -37,6 +51,9 @@ function multiexec($thread, $x, $null = true) {
$exec = implode(" & ",$cmd)." & wait";
}
echo $echo;
if ($debug) {
echo "\n\n".$exec."\n\n";
}
exec($exec);
}
}
@@ -99,6 +116,8 @@ function args($query) {
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 {
@@ -158,6 +177,7 @@ $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=<num> (6) specify how many files to use
@@ -168,11 +188,17 @@ deskew detect rotation angles
-contrast=<num> (20) levels attenuation
-size=<num> (2200) dmap size in pixels
-nomap don't use dmaps
despine remove shadow from spine
-target=<file> only process pages with spine similar to the spine of this target file
-levels=<params> levels for overlay (ex. \"0-253,0-255,0-2\")
-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
-adjust=<params> levels adjustment (ex. \"0%,98%,.9\")
-levels=<params> photoshop levels adjustment (ex. \"0-253,0-255,0-2\")
-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)
@@ -182,10 +208,9 @@ 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
-adjust=<params> levels adjustment (ex. \"0%,98%,.9\")
-levels=<params> photoshop levels adjustment (ex. \"0-253,0-255,0-2\")
-px=<num> png size multiplier
-padjust=<params> png levels adjustment (otherwise calculated from adjust)
makepdf combine images into a pdf with img2pdf
-plevels=<params> png levels adjustment (otherwise calculated from adjust)
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)
@@ -208,6 +233,143 @@ 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: 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");
}
//if (!args("target") || !is_file(args("target"))) {
// msg("Specify a target file to match spine",1);
// } else {
// $target = args("target");
// }
//$ext = pathinfo($target,PATHINFO_EXTENSION);
//$tspine_left = "scratch/".basename($target, ".".$ext)."_spineleft.".$ext;
//$tspine_right = "scratch/".basename($target, ".".$ext)."_spineright.".$ext;
//list ($width, $height) = getimagesize($target);
//$even = 0;
//if (substr(basename($target), 0, 3) % 2 == 0) {
// $geo = "+".($width-$swidth)."+0";
// $cropsize = $swidth."x".$height;
// $crop = $cropsize.$geo;
// $even = 1;
// } else {
// $geo = "+0+0";
// $cropsize = $swidth."x".$height;
// $crop = $cropsize.$geo;
// }
//
//if ($even) {
// exec("convert ".$target." -crop ".$crop." -flop ".$tspine_left);
// exec("convert ".$target." -crop ".$crop." ".$tspine_right);
// } else {
// exec("convert ".$target." -crop ".$crop." ".$tspine_left);
// exec("convert ".$target." -crop ".$crop." -flop ".$tspine_right);
// }
$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;
// $mytspine = $tspine_right;
} else {
$geo = "+0+0";
$cropsize = $swidth."x".$height;
$crop = $cropsize.$geo;
// $mytspine = $tspine_left;
}
// $filespine = "/tmp/".basename($file,$ext)."-spine.".$ext;
// if (!file_exists($filespine)) {
// exec("convert ".$file." -auto-orient -crop ".$crop." ".$filespine);
// }
// $distance = chop(shell_exec("compare -metric phash ".$mytspine." ".$filespine." /tmp/diffimage 2>&1"));
// if ($distance < $threshold) {
// $print_distance = bashcolor($distance,"green");
// } else {
// $print_distance = $distance;
// }
// msg("Comparing ".$mytspine." with ".$filespine.": ".$print_distance);
if (args("q")) {
$quality = args("q");
} elseif (strtolower($ext) == "jpg") {
$quality = chop(shell_exec("identify -format '%Q' ".$file));
} else {
$quality = $default_quality;
}
//if ($distance < $threshold) {
$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;
}
//} else {
//$msg = $file." <skip>";
//$cmd = "true";
//}
$thread[] = array($msg, $cmd);
}
echo "\n\n";
msg("Beginning multithreaded convert with ".$xt." threads");
multiexec($thread,$xt);
fin();
////////////////////////////////////////////////////////////////////////////////////////////////
// Note: Match
//////////////////////
@@ -221,6 +383,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);
@@ -231,7 +396,10 @@ foreach ($files as $file) {
if (basename($file,$ext) != basename($dest,$dparts['extension'])) {
$newdest = $dparts['dirname']."/".$key."-".$dparts['basename'];
msg("Moving ".$dest." to ".$newdest);
//rename($dest,$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);
@@ -243,7 +411,8 @@ foreach ($files as $file) {
$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." ".$file);
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");
}
@@ -253,6 +422,11 @@ foreach ($files as $file) {
}
echo "\n";
msg("Moved ".$i." files");
msg("Tagged ".$ix." files");
fin();
////////////////////////////////////////////////////////////////////////////////////////////////
@@ -410,9 +584,9 @@ foreach ($jpg as $parts) {
}
if (args("adjust")) {
$msg .= " (".args("adjust").")";
$cmd .= "-level ".args("adjust")." ";
if (args("levels")) {
$msg .= " (".args("levels").")";
$cmd .= "-level ".args("levels")." ";
}
$msg .= " [Q=".$quality."]";
@@ -441,8 +615,8 @@ if (isset($bitmap)) {
$msg = "Processing bitmap page ".$file." @ ".$pngdims."!";
$cmd = "convert -resize ".$pngdims."\! ";
if (args("adjust")) {
$adjust = args("adjust");
if (args("levels")) {
$adjust = args("levels");
} else {
$adjust = "0%,100%,1";
}
@@ -452,8 +626,8 @@ if (isset($bitmap)) {
$white = rtrim($parts[1],"%");
$gamma = $parts[2];
if (args("padjust")) {
$radjust = args("padjust");
if (args("plevels")) {
$radjust = args("plevels");
} else {
$radjust = ($black+60)."%".",".($white-15)."%".",".($gamma-.7);
}
@@ -564,7 +738,7 @@ foreach ($files as $file) {
if (args("q")) {
$quality = args("q");
} elseif (strtolower($ext) == "jpg") {
$quality = @exec("identify -format '%Q' ".$file);
$quality = chop(shell_exec("identify -format '%Q' ".$file));
} else {
$quality = $default_quality;
}
@@ -1046,9 +1220,9 @@ foreach ($files as $file) {
fin();
////////////////////////////////////////////////////////////////////////////////////////////////
// Note: Makepdf
// Note: Build
//////////////////////
} elseif (args("app") == "makepdf") {
} elseif (args("app") == "build") {
echo Welcome("Combine finished images into a pdf with python img2pdf");
/////////////////////////////////////////////////////////////////////////
@@ -1144,6 +1318,11 @@ if (!args("map")) {
$map = args("map");
}
if (args("mpush")) {
$map = basename(args("map"),".JPG")."-pushed.tif";
@exec("convert -level ".levels_ps_to_imgk(args("mpush"))." ".args("map")." ".$map);
}
$dest = rtrim(args("dir"), '/')."_divided";
if (count(glob($dest."/*.*"))) {
@@ -1164,8 +1343,9 @@ if ($pages) {
$ext = pathinfo($map, PATHINFO_EXTENSION);
$mapleft = "scratch/".str_replace(".".$ext, "_left.".$ext, $map);
$mapright = "scratch/".str_replace(".".$ext, "_right.".$ext, $map);
exec("convert -rotate 90 -quality 95 ".$map." ".$mapleft);
exec("convert -rotate 270 -quality 95 ".$map." ".$mapright);
//@exec("convert -rotate 90 -quality 95 ".$map." ".$mapleft." 2>&1");
@exec("convert -rotate 90 -flip -quality 95 ".$map." ".$mapleft." 2>&1");
@exec("convert -rotate 270 -quality 95 ".$map." ".$mapright." 2>&1");
}
echo "Building threads: ";
@@ -1174,7 +1354,7 @@ $thread = array();
foreach ($files as $file) {
$msg = "Dividing ".$file." with ".$map.", Q=".$quality;
$cmd = "convert ".$file." ";
$cmd = "convert ".$file;
$output = $dest."/".basename($file);
if ($pages) {
@@ -1209,16 +1389,30 @@ foreach ($files as $file) {
}
$cmd .= $tmap." -compose Divide_Src -composite -quality ".$quality." ";
if (args("blur")) {
$msg .= " (".args("blur").")";
$overlay = "\( ".$tmap." -blur ".args("blur")." \)";
} else {
$overlay = $tmap;
}
if (args("adjust")) {
$msg .= " (".args("adjust").") ";
$cmd .= "-level ".args("adjust")." ";
$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);
}