Skip to content

Instantly share code, notes, and snippets.

@askaidau
Last active April 1, 2017 09:55
Show Gist options
  • Save askaidau/55193b288fa7e757b40a7b4a80e15d3a to your computer and use it in GitHub Desktop.
Save askaidau/55193b288fa7e757b40a7b4a80e15d3a to your computer and use it in GitHub Desktop.
Image Compactr (PHP), requires pngquant
#!/usr/bin/php
<?php
if( "cli"!==php_sapi_name() ) exit;
if( !isset($argv,$argv[1]) ){
cli_error("usage: ./".basename(__FILE__)." <path>");
cli_error("example: ./".basename(__FILE__)." /data/sites/example.com/fat-images/");
exit;
}
$path_base = rtrim($argv[1],"/");
# settings
define('VERSION', '1.0.2');
define('GIST_URL', 'https://gist.github.com/askaidau/55193b288fa7e757b40a7b4a80e15d3a');
define('VERBOSE', false);
define('RECURSIVE', true);//do sub-folders
define('DONE_FILE', 'img-compressed.done');
define('DEBUG_ONLY', false);//true = dryrun
define('BACKUP', false);//keep copy of original as {filename}.bak
define('SLIM_JPG', true);
define('JPG_QUALITY', 80);
define('JPG_MIN_SIZE',80*1024);// only compress files > 80 KB
define('JPG_MAX_W', 1920);//resize if image exceeds this width
define('SLIM_PNG', true);
define('PNG_QUALITY', 80);
define('PNG_MIN_SIZE',50*1024);// only compress files > 50 KB
# cli colors
define( 'CLI_COLOR_RED', "\033[31m" );
define( 'CLI_COLOR_YELLOW', "\033[1;33m" );
define( 'CLI_COLOR_GREEN', "\033[32m" );
define( 'CLI_COLOR_CYAN', "\033[36m" );
define( 'CLI_COLOR_RESET', "\033[0m" );
# this is really happening
cli_error( cli_color_str('blue','[info]')." Version ".VERSION );
cli_error( cli_color_str('blue','[info]')." About to compress images inside '$path_base/' ...");
sleep(3);
compress_dir_images( $path_base );
function compress_dir_images( $path_base ) {
# checks
if( ! file_exists($path_base) || ! is_writable($path_base) ) {
cli_error("[error] Path does not exist or is not writable!"); exit;
}
# init done file
$path_done_file = "$path_base/".DONE_FILE;
if( ! file_exists($path_done_file) ) {
touch($path_done_file);
$done_files = [];
}
else {
$done_files = array_map('trim',file($path_done_file));
}
$done_fp = fopen( $path_done_file, 'a' );
if( $dir_handle = opendir($path_base) ) {
$total_old_bytes = 0;
$total_new_bytes = 0;
$file_bytes_saved = [];
$sub_dirs = [];
while( false !== ($filename = readdir($dir_handle) ) ) {
// the big loop
$path_file = "$path_base/$filename";
if( preg_match('!\.(jpg|jpeg|png)!i',$filename,$matches)==1 ) {
if( strpos($filename,'-bak.') !== false ) {
// ignore -bak.{ext} files
continue;
}
# already processed?
if( in_array($filename, $done_files) ) {
continue;
}
$filesize = filesize($path_file);
$ext = $matches[1];
switch( $ext ) {
case 'jpg':
case 'jpeg':
case 'JPG':
case 'JPEG':
if( true !== SLIM_JPG ) continue 2;
if( $filesize < JPG_MIN_SIZE ) {
if( true === VERBOSE ) cli_error("[info] $filename skipped (".get_kb($filesize)."k)");
continue 2;
}
$slimmed = @app_compress_jpg($path_file, JPG_QUALITY);
break;//jpg
case 'png':
case 'PNG':
if( true !== SLIM_PNG ) continue 2;
if( $filesize < PNG_MIN_SIZE ) {
if( true === VERBOSE ) cli_error("[info] $filename skipped (".get_kb($filesize)."k)");
continue 2;
}
$slimmed = @app_compress_png($path_file, PNG_QUALITY);
break;
default:
continue 2;
}
$slimsize = strlen($slimmed);
if( $slimsize==0 ) {
cli_error( cli_color_str('red','[error]')." failed slimming file: $filename" );
continue;
}
$reduct_ratio = round( ( $filesize-$slimsize ) / $filesize * 100 , 0 );
if( $reduct_ratio < 15 ) {
cli_error( cli_color_str('red','[error]')." ineffective reduction in size for $filename ($reduct_ratio%), skipping..." );
continue;
}
cli_error("[info] $filename ".get_kb($filesize)."k => ".get_kb($slimsize)."k ($reduct_ratio%)");
# record savings
$total_old_bytes += $filesize;
$total_new_bytes += $slimsize;
$file_bytes_saved[] = ($filesize - $slimsize);
# backup
if( true === BACKUP ) {
$path_backfile = str_replace( ".$ext", "-bak.$ext", $path_file);
copy($path_file, $path_backfile);
}
if( true === DEBUG_ONLY ) {
# TEST ONLY -- test slim output
$path_testfile = str_replace( ".$ext", "-slim.$ext", $path_file);
file_put_contents($path_testfile, $slimmed);
cli_error( cli_color_str('blue','[slimmed]')." $path_testfile");
}
else {
# this is the real deal
file_put_contents($path_file, $slimmed);
fwrite($done_fp, $filename.PHP_EOL);
}
unset($slimmed);//reclaim memory hopefully?
if( count($file_bytes_saved)%50==0 ) {
cli_error( cli_color_str('blue','[info]').' '.count($file_bytes_saved).' files compressed' );
sleep(3);
}
}
else {//not image file
if( ! in_array($filename,['.','..']) && true === RECURSIVE && is_dir($path_file) ) {
$sub_dirs[] = $path_file;
}
}
}//while
closedir($dir_handle);
fclose($done_fp);
# produce summary
cli_error(cli_color_str('green','[summary]')." Before: ".get_mb($total_old_bytes)."m");
cli_error(cli_color_str('green','[summary]')." After: ".get_mb($total_new_bytes)."m");
cli_error(cli_color_str('green','[summary]')." Reduced: ".get_mb(array_sum($file_bytes_saved))."m; over ".count($file_bytes_saved)." files");
sleep(5);
// process sub directories if any
if( count($sub_dirs)>0 ) {
foreach( $sub_dirs as $sub_dir ) {
cli_error("[info] processing sub-dir '$sub_dir'");
sleep(3);
compress_dir_images($sub_dir);
}
}//if count($sub_dirs)>0
}//if $dir_handle
}//function compress_dir_images
# utilities
function cli_error($msg)
{
fwrite(STDERR,"[".date("Y-m-d H:i:s")."] $msg".PHP_EOL);
}
function cli_color_str($color="",$str="") {
switch($color){
case 'red': return CLI_COLOR_RED.$str.CLI_COLOR_RESET;
case 'yellow': return CLI_COLOR_YELLOW.$str.CLI_COLOR_RESET;
case 'green': return CLI_COLOR_GREEN.$str.CLI_COLOR_RESET;
case 'blue': case 'cyan': return CLI_COLOR_CYAN.$str.CLI_COLOR_RESET;
}
return $str;
}
function app_compress_jpg( $path_to_jpg_file, $quality=80 )
{
$img_size = getimagesize($path_to_jpg_file);
$w = $img_size[0];
$h = $img_size[1];
$img_src = imagecreatefromjpeg($path_to_jpg_file);
if( $w > JPG_MAX_W ) {
$new_w = JPG_MAX_W;
$new_h = round( ($new_w/$w)*$h );
$img_new = imagecreatetruecolor( $new_w, $new_h );
imagecopyresampled( $img_new, $img_src, 0, 0, 0, 0, $new_w, $new_h, $w, $h );
unset($img_src);
$img_src = $img_new;
}
ob_start();
imagejpeg( $img_src, null, $quality );
return ob_get_clean();
}//app_compress_jpg
function app_compress_png( $path_to_png_file, $max_quality=90 )
{
if (!file_exists($path_to_png_file)) {
throw new Exception("File does not exist: $path_to_png_file");
}
// guarantee that quality won't be worse than that.
$min_quality = 70;
$compressed_png_content = shell_exec("pngquant --quality=$min_quality-$max_quality - < ".escapeshellarg($path_to_png_file));
if (!$compressed_png_content) {
throw new Exception("Conversion to compressed PNG failed. Is pngquant 1.8+ installed on the server?");
}
return $compressed_png_content;
}//app_compress_png
function get_kb( $bytes=0, $decimals=1 ) {
return round($bytes/1024, $decimals);
}
function get_mb( $bytes=0, $decimals=1 ) {
return round($bytes/(1024*1024), $decimals);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment