#!/usr/bin/env bash
################################## INFO #####################################
# A GNU Bash script to automatically TRY to compress PDF files in the current directory.
# Copyright (C) 2013-2021 Lilian BESSON (lbesson at ens-cachan dot fr)
#
# Find the latest version on https://perso.crans.org/besson/bin/PDFCompress
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see ,
# or .
################################# CAUTION ###################################
# Use with caution: sometime, the new PDF is bigger than the first one.
# Good advice: in a git repository, compress before storing => a real improvement !
##############################################################################
version="1.5.1"
##LANG="en"
GREP="grep --color=always --line-buffered"
BIN=$(basename "$0")
ECHO="echo -e"
# If editing the meta data, change them to say that the PDF have been modified.
# in fact, it is the default behavior :)
## FIXME: if using '{' or '/' in $creator, warning when using sed ...
creator="Compressed with https://perso.crans.org/besson/bin/PDFCompress v$version -- a script by Lilian BESSON (C) 2013-2021"
creator="$($ECHO "$creator" | tr '{' '_')"
# If possible, use .color.sh (https://perso.crans.org/besson/bin/.color.sh or https://bitbucket.org/lbesson/bin/src/master/.color.sh)
if [ -f "$HOME/.color.sh" ]; then
. "$HOME/.color.sh"
else
$ECHO "\033[01;31m WARNING: ~/.color.sh have not been found...\033[01;37m" > /dev/stderr
$ECHO "\033[01;33m INFO: 1) You can download it on https://perso.crans.org/besson/bin/.color.sh or https://bitbucket.org/lbesson/bin/src/master/.color.sh \033[01;37m" > /dev/stderr
$ECHO "\033[01;33m INFO: 2) Then move it to "$"HOME/.color.sh (for example with 'mv .color.sh ~/.color.sh' ...) \033[01;37m" > /dev/stderr
$ECHO "\033[01;33m INFO: 3) And finally, make it executable: 'chmod +x ~/.color.sh' . Now you can have colors in $BIN :) \033[01;37m" > /dev/stderr
fi
# Default quality. FIXME: in fact, does not change very much the result....
quality="ebook"
# Image resolution (the smaller this is, the smaller the new PDF will be, but the quality will decrease)
# FIXME add one option of the command line for this
# ImageResolution="130"
ImageResolution="800"
# False to simply change the meta data (and sign ?)
compress="true"
# True to force compression (even if pdfinfo said that Ghostscript or PDFCompress
# have already been used to compress the file.
force="false"
# Keep meta data : enabled by default.
keep="true"
# Sign the new PDF : disabled by default.
sign="false"
# Control the verbosity of gs. By default, quiet.
quiet="-q"
# To control the use of notify-send. By default, use it.
notify="true"
# To control the use of zenity. By default, do not use it.
zenity="false"
icon=""
NOTIFY=""
ZENITY=""
PKILL="echo"
########################## Copyrights and options ##############################
$ECHO "${green}$BIN v$version : Copyright (C) 2013-2021 Lilian BESSON"
$ECHO "This is free software, and you are welcome to redistribute it under certain conditions."
$ECHO "This program comes with ABSOLUTELY NO WARRANTY; for details see https://perso.crans.org/besson/LICENSE ${white}"
############################# Argument handling ################################
for i in "$@"
do
case "$i" in
-V|--version)
$ECHO "$BIN $version"
exit 0
;;
-h|--help)
$ECHO "$green$BIN$white --help | [options]"
$ECHO ""
$ECHO "Compress every PDF files in the current directory, using gs (part of the Ghostscript software)."
$ECHO "Help:"
$ECHO " ${yellow}-h|--help$white to print this help message (and quit)."
$ECHO " ${yellow}-v|--version$white to print just the version of $BIN (and quit)."
$ECHO ""
$ECHO "Options:"
$ECHO " ${yellow}-q|--quality QUALITY$white setting the compression quality to QUALITY."
$ECHO " ${yellow}screen${white} High compression, low quality. Choose this to get the best compression."
$ECHO " ${yellow}ebook${white} High compression, mean quality. Maybe the best option :) (Default behavior)"
$ECHO " ${yellow}printer${white} Mean compression, mean quality."
$ECHO " ${yellow}prepress${white} Low compression, high quality."
$ECHO ""
$ECHO " ${yellow}-nk|--no-keep$white erase the meta data of all PDF files. By default $BIN keeps the meta data (requires pdftk)."
$ECHO ""
$ECHO " ${yellow}-s|--sign$white sign all the new version of the PDF files, with GnuPG ($sign by default)."
$ECHO ""
$ECHO " ${yellow}-nc|--no-compress$white do not compress the PDF files (default is to compress it)."
$ECHO ""
$ECHO " ${yellow}-f|--force$white force the compression process. Default is to compress only if it has not already been compressed (still a little experimental)."
$ECHO ""
$ECHO " ${yellow}-nn|--no-notify$white disable notification sent by notify-send (enabled by default)."
$ECHO ""
$ECHO " ${yellow}-nz|--no-zenity$white disable graphical progress window (with zenity) during the compression (enabled by default)."
$ECHO ""
$ECHO " ${yellow}-v|--verbose$white increase verbosity of gs (quiet by default, option '-q')."
$ECHO ""
$ECHO "$BIN v$version : Copyrights: (c) Lilian Besson 2011-2021."
$ECHO "Released under the term of the GPL v3 Licence (more details on https://perso.crans.org/besson/LICENSE.html)."
$ECHO "In particular, $BIN is provided WITHOUT ANY WARANTY."
exit 0
;;
-q|--quality)
$ECHO "${cyan} option quality found.${white}"
shift
quality="$1"
$ECHO "${cyan} compressing with the quality $u$quality$U.${white}"
shift
;;
--no-keep)
$ECHO "${cyan} option no-keep found.${white}"
shift
keep="false"
;;
-k|--keep)
$ECHO "${cyan} option keep found.${white}"
shift
keep="true"
;;
-s|--sign)
$ECHO "${cyan} option sign found.${white}"
shift
sign="true"
;;
-f|--force)
$ECHO "${cyan} option force found.${white}"
shift
force="true"
;;
-nc|--no-compress)
$ECHO "${cyan} option no-compress found.${white}"
shift
compress="false"
;;
-nn|--no-notify)
$ECHO "${cyan} option no-notify found.${white}"
shift
notify="false"
;;
-nz|--no-zenity)
$ECHO "${cyan} option no-zenity found.${white}"
shift
zenity="false"
PKILL="echo"
;;
-v|--verbose)
$ECHO "${cyan} option verbose found.${white}"
shift
quiet=""
;;
*)
$ECHO "${white} Argument $i may have been NOT used.${white}" > /dev/null
## FIXME
;;
esac
done
##################### Auto detection of needed softwares #######################
# $ECHO " Auto detection of needed softwares...."
# # Detection of gs
# ( ( gs --help | head -n1 | $GREP "Ghostscript" ) && $ECHO "$green - gs is in your $"PATH" :)$reset$white" ) || \
# ( $ECHO "$red Error:$white gs is not in your $"PATH" :(" > /dev/stderr ; exit 5 )
# # Detection of pdftk
# ( pdftk --help | $GREP "^pdftk" && $ECHO "$green - pdftk is in your $"PATH" :)$reset$white" ) || \
# ( $ECHO "$red Error:$white pdftk is not in your $"PATH" :(" > /dev/stderr ; touch "/tmp/nopdftk" )
# if [ -f "/tmp/nopdftk" ]; then
# keep="false"
# fi
# # Detection of gpg
# ( ( gpg --version | head -n1 | $GREP "^gpg" ) && $ECHO "$green - gpg is in your $"PATH" :)$reset$white" ) || \
# ( $ECHO "$red Error:$white gpg is not in your $"PATH" :(" > /dev/stderr ; touch "/tmp/nogpg" )
# if [ -f "/tmp/nogpg" ]; then
# sign="false"
# fi
# # Detection of notify-send
# ( ( notify-send --version | head -n1 | $GREP "^notify-send" ) && $ECHO "$green - notify-send is in your $"PATH" :)$reset$white" ) || \
# ( $ECHO "$red Error:$white notify-send is not in your $"PATH" :(" > /dev/stderr ; touch "/tmp/nonotify-send" )
# if [ -f "/tmp/nonotify-send" ]; then
# notify="false"
# else
icon="-i $(ls -H /usr/share/icons/*/*/*/*pdf*svg | uniq | shuf | head -n1) "
NOTIFY="notify-send -t 10000 $icon $BIN"
# fi
# # Detection of zenity
# ( ( zenity --version | $GREP "^3\..*" ) && $ECHO "$green - zenity is in your $"PATH" :)$reset$white" ) || \
# ( $ECHO "$red Error:$white zenity is not in your $"PATH" :(" > /dev/stderr ; touch "/tmp/nozenity" )
# ##if [ -f "/tmp/nozenity" || ( "X$DISPLAY" = "X" ) ]; then
# if [ -f "/tmp/nozenity" ]; then
# zenity="false"
# else
# ZENITY="LANG=fr_FR.UTF-8 zenity --progress --pulsate --auto-kill --auto-close --title=\"$BIN $version\" --text=\"Compressing '$i'... \nWorking in `pwd`,\n Storing metadata to '$meta'\nPress 'Cancel' to .. cancel :)\""
# fi
if [ "X$DISPLAY" = "X" ]; then
notify="false"
zenity="false"
fi
###################### Print infos about the parameters ########################
$ECHO ""
$ECHO "${cyan} Using quality=$white$quality$cyan. See $BIN --help to get a list of different possible parameters.${white}"
[ "$keep" = "true" ] && $ECHO "${cyan} Trying to keep all meta datas...${white}"
[ "$keep" = "false" ] && $ECHO "${cyan} Do not trying to keep all meta datas...${white}"
[ "$sign" = "true" ] && $ECHO "${cyan} Trying to sign all pdf files...${white}"
[ "$sign" = "false" ] && $ECHO "${cyan} Do not trying to sign all pdf files...${white}"
[ "$compress" = "true" ] && $ECHO "${cyan} Trying to compress all pdf files...${white}"
[ "$compress" = "false" ] && $ECHO "${cyan} Do not trying to compress all pdf files...${white}"
$ECHO "${cyan} Using $reset'$white$creator'$cyan as the new Hyperref value for the 'Creator' item.${white}"
if [ "$quiet" != "" ]; then
GS="/usr/bin/gs $quiet -dNOPAUSE -dBATCH -dSAFER -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dEmbedAllFonts=true -dSubsetFonts=true -dColorImageDownsampleType=/Bicubic -dColorImageResolution=${ImageResolution} -dGrayImageDownsampleType=/Bicubic -dGrayImageResolution=${ImageResolution} -dMonoImageDownsampleType=/Bicubic -dMonoImageResolution=${ImageResolution} -dPDFSETTINGS=/"$quality""
$ECHO "${cyan} Being quiet...${white}"
else
GS="/usr/bin/gs -dNOPAUSE -dBATCH -dSAFER -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dEmbedAllFonts=true -dSubsetFonts=true -dColorImageDownsampleType=/Bicubic -dColorImageResolution=${ImageResolution} -dGrayImageDownsampleType=/Bicubic -dGrayImageResolution=${ImageResolution} -dMonoImageDownsampleType=/Bicubic -dMonoImageResolution=${ImageResolution} -dPDFSETTINGS=/"$quality""
$ECHO "${cyan} Being verbose...${white}"
fi
if [ "X$notify" = "Xtrue" ]; then
[ "$icon" != "" ] && $ECHO "$cyan For notify-send, using $reset'$white$icon'"
[ "$NOTIFY" != "" ] && $ECHO "$cyan For notify-send, using the command $reset'$white$NOTIFY'"
fi
if [ "X$zenity" = "Xtrue" ]; then
[ "$ZENITY" != "" ] && $ECHO "$cyan For zenity, using the command $reset'$white$ZENITY'"
fi
[ "$GS" != "" ] && $ECHO "$cyan For gs, using the command $reset'$white$GS'..."
############################## Main function ###################################
PDFCompress ()
{
$ECHO "\n${white} File selection...."
arg=""
argt=""
for i in "$@"; do
# Make it work with file containing dirnames
fichier="$(basename "$i")"
dossier="$(dirname "$i")"
fichier="${fichier%.pdf}.pdf"
pdosser="`pwd`/${dossier}"
$ECHO "${reset}${white}${u}$i${U}"
$ECHO "${reset}${white}The dirname part is ${u}$dossier${U} and the basename part is ${u}$fichier${U}."
if [ -f "$i" ] || [ -f "${i%.pdf}.pdf" ]; then
$ECHO "${white}${Black}For $u$i$U${reset}:\
$(pdfinfo "$i" 2>&1 | GREP_COLOR="01;33" $GREP -m1 "\(Title\|Error\)" )${reset}${white}."
pdfinfo "$i" &>/dev/null
if [ "X$?" = "X0" ]; then
[ ! -f "${i%.pdf}.pdf" ] && $ECHO "${red}Warning${yellow}: file $i seems to be a valid PDF file but has not a .pdf file extension${white}" > /dev/stderr
arg="$arg $i"
$ECHO "${green}... adding $i as a valid PDF file${white}"
argt="$argt $i~"
else
$ECHO "${red}$BIN: cannot use $i: is not a valid PDF file${white}" > /dev/stderr
fi
else
$ECHO "${red}$BIN: cannot access $i: No such file or directory${white}" > /dev/stderr
fi
done
arg="${arg# }"
argt="${argt# }"
$ECHO "\n${white} Files selected : $arg"
# Print the comparaison between size at the end.
log1="/tmp/PDFCompress_before.log"
log2="/tmp/PDFCompress_after.log"
du -kh "$arg" > "$log1"
$ECHO "${white}Compressing $arg ...${white}";
[ "$(/bin/ls "$argt" 2>/dev/null)" != "" ] && /bin/mv -f -v "$argt" /tmp/;
for i in $arg; do
$ECHO "${blue}For the file ${green}$i${white}...";
# Make it work with file containing dirnames
fichier="$(basename "$i")"
dossier="$(dirname "$i")"
fichier="${fichier%.pdf}.pdf"
pdosser="$(pwd)/${dossier}"
meta="/tmp/${fichier}.meta"
if [ "$keep" = "true" ]; then
[ -f "$meta" ] && rm -fv "$meta"
( pdftk "$i" dump_data output "$meta" \
&& $ECHO "$green meta data for $i well written to $meta :)$reset$white" \
&& line=$(cat "$meta" | grep -n Creator | grep -o -m1 ^[0-9]*) \
&& (
cat "$meta" | sed $((line+1))s{InfoValue:.*{"InfoValue: $creator"{ > "$meta"~ \
) \
&& /bin/cp -fv "$meta"~ "$meta" \
) || \
$ECHO "$red Error:$white meta data for $i not written to $meta :(" > /dev/stderr ;
fi
if [ "X$force" = "Xtrue" ]; then
$ECHO "${yellow}Info:$white Forcing compression ..."
[ "X$compress" != "Xtrue" ] && $ECHO "${red}Error:$white Compression have been forced, this bypass the option $compress which was set to $compress (not true)."
compress="true"
else
if [ "X$compress" = "Xtrue" ]; then
$ECHO "${yellow}Info:$white Not forcing compression."
if ( pdfinfo "$i" | grep "\(Ghostscript\|PDFCompress\)" ); then
$ECHO "${yellow}Info:$white The file ${u}'${i}'${U} seems to have already been compressed with Ghostscript, or $0 : ${red}NOT${white} recompressing it ..."
compress="false"
else
$ECHO "${yellow}Info:$white The file ${u}'${i}'${U} seems to have not already been compressed with Ghostscript, nor $0 : ${green}recompressing it${white} ..."
fi
fi
fi
if [ "X$compress" = "Xtrue" ]; then
$ECHO "${yellow}Info:$white starting compression for $i...$reset$white";
$ECHO "$cyan Info: using $"GS"=${GS} as the gs command...$reset$white";
if [ "X$zenity" = "Xtrue" ]; then
LANG="fr_FR.UTF-8"
$ECHO "Starting zenity interactive window."
## The --auto-kill was not working as I thought it was. Too bad.
( $ECHO "0"; $GS -sOutputFile="$i"~ "$i"; $ECHO "100"; ) | \
( zenity --progress --pulsate --auto-close \
--title="$BIN v$version (program by Lilian Besson)" \
--text="Compressing the PDF '$i'... \nWorking in the directory $(pwd),\n Metadata stored to '$meta'\n\nPress 'Cancel' to try to cancel the operation (still experimental)." || ( $ECHO "${red}Error:${white} Canceled by you ? Well anyway, I failed, so I will try to restore the backup PDF from /tmp/ ..."; $PKILL gs; cp -fv ./"$i" ./"$i"~ ; return 8; ) ; \
$ECHO "Done"; $PKILL gs; )
# || ($PKILL gs; export ANS="8"; $ECHO "Fail, would you like to restore the backup ?"; cp -vi /tmp/"$i" ./"$i" exit 8);
else
$ECHO "0%" ; $GS -sOutputFile="$i"~ "$i"; $ECHO "100%"
fi
else
$ECHO "${yellow}Info:$white starting copy for $i (emulated compression :p)..."
/bin/cp -fv "$i" "$i"~
fi
if [ -f "$i"~ ]; then
/bin/cp "$i" /tmp/;
/bin/ls -hF --color=auto -s "$i" "$i"~;
/bin/cp -f -v "$i"~ "$i";
if [ "$keep" = "true" ]; then
( pdftk "$i"~ update_info "$meta" output "$i" \
&& $ECHO "$green meta data for $i well re-written from ${meta} :)$reset$white"
) || \
$ECHO "$red Error:$white meta data for $i not re-written from ${meta} :(" > /dev/stderr
fi
if [ "$sign" = "true" ]; then
gpg --quiet --armor --detach-sign --yes --no-batch --use-agent "$i"
fi;
/bin/mv -f -v "$i"~ /tmp/;
/bin/rm -v -f gs_*;
else
$ECHO "$red Error: $i~ no present (ie. the compression ${u}FAILED${U})... :($reset$white :(" > /dev/stderr
fi;
# Check that the file's size have been really reduced.
sizeold=$(/usr/bin/du -b "/tmp/$i" 2>/dev/null | grep -o -m 1 "^[0-9][,0-9]*")
sizenew=$(/usr/bin/du -b "$i" 2>/dev/null | grep -o -m 1 "^[0-9][,0-9]*")
if (( sizeold < sizenew )); then
$ECHO "\n${red} ${neg}Maybe${Neg} the compression failed, because \n - the new file (${i} of size ${sizenew} bytes) seems larger than \n - the old one (/tmp/${i} of size ${sizeold} bytes). ${white}" > /dev/stderr
else
$ECHO "\n${green} The compression (${neg}probably${Neg}) worked, because \n - the new file (${i} of size ${sizenew} bytes) seems smaller than \n - the old one (/tmp/${i} of size ${sizeold} bytes). ${white}"
# Notify the user if the compression worked
if [ "$notify" = "true" ]; then
$ECHO "$green - notify-send: notifying..."
$NOTIFY "Success :)\n The file $i have been compressed.\n Sizes: before : $(/usr/bin/du -kh "/tmp/$i" 2>/dev/null | grep -o -m 1 "^[0-9][,0-9]*[KMG]*") ---> and after : $(/usr/bin/du -kh "$i" 2>/dev/null | grep -o -m 1 "^[0-9][,0-9]*[KMG]*")"
fi
fi
done;
/bin/rm -v -f gs_*
du -kh "$arg" > "$log2"
$ECHO "${blue}Difference between sizes before compression and after compression :${reset}${white}"
diff --side-by-side "$log1" "$log2"
# $ECHO "${yellow} You would like to restore backups for the following files ? Still in beta ...${white}"
exit 0
}
# Finally, run the function on the remaining args of the script.
PDFCompress "$@"
################################### END #######################################