#!/bin/sh -eu
# Dependencies: util.sh, archive_extract.sh, archive_create.sh, optipng,
# imagemagick-7, parallel, waifu2x-converter-cpp
# Optional: img2pdf, natsort (both for PDF output)
# Portability: GNU, *BSD, MacOS, Illumos
# +-------------+-----+---------+--------+--------------+---------+-------+---------+-------+-----+------+-------+
# | cmd/OS | GNU | OpenBSD | NetBSD | DragonflyBSD | FreeBSD | MacOS | Illumos | HP-UX | AIX | IRIX | Tru64 |
# +-------------+-----+---------+--------+--------------+---------+-------+---------+-------+-----+------+-------+
# | find -iname | o | o | o | o | o | o | o | | | | |
# +-------------+-----+---------+--------+--------------+---------+-------+---------+-------+-----+------+-------+
# | mktemp -d | o | o | o | o | o | o | o | X | | | o |
# +-------------+-----+---------+--------+--------------+---------+-------+---------+-------+-----+------+-------+
# | xargs -0 | o | o | o | o | o | o | o | | | | |
# +-------------+-----+---------+--------+--------------+---------+-------+---------+-------+-----+------+-------+
# Check if local or typeset is available
if ! sh -c 'fun() { local a=1; }; fun'
then
if sh -c 'fun() { typeset a=1; }; fun'
then
alias local=typeset
else
echo "local/typeset not supported by sh, aborting"
exit 1
fi
fi
if command readlink --version 2>/dev/null | grep -qF '^readlink (GNU coreutils)'
then
alias preadlinkf='readlink -f --'
elif command -v greadlink >/dev/null
then
alias preadlinkf='greadlink -f --'
else
# Portable readlink -f using GNU's semantics (last component need not exist)
# Currently passes the tests from https://github.com/ko1nksm/preadlinkf
# except loop detection (`getconf SYMLOOP_MAX` is undefined here, anyway)
preadlinkf()
{
[ $# -eq 0 ] && return 1
local i= status=0 pwd="$(pwd -P)" base= dir=
for i
do
! [ "$i" ] && { status=1; continue; }
if [ -d "$i" ]
then
CDPATH= cd -P -- "$i" >/dev/null 2>&1 || return 1
pwd -P
else
case "$i" in */) [ -e "${i%/}" ] && return 1;; esac
while true
do
CDPATH= cd -P -- "$(dirname -- "$i")" >/dev/null 2>&1 || return 1
base=${i%/}
base=${base##*/}
if [ -L "$base" ]
then
i=$(ls -l -- "$base")
i=${i#*"$base -> "}
else
dir=$(pwd -P)
dir=${dir%/}
printf '%s/%s\n' "$dir" "$base"
break
fi
done
fi
cd -- "$pwd"
done
return $status
}
fi
selfdir=$(dirname -- "$(preadlinkf "$0")")
PATH=$PATH:$selfdir
. "$selfdir"/util.sh
requirebin optipng magick parallel waifu2x-converter-cpp
usage()
{
name=$(basename "$(preadlinkf "$0")")
cat <<EOF | text_format
**NAME**
$name - Convert an archived manga for ereader usage
**SYNOPSIS**
$name [**-hsnw**] [**-d** __DIMENSIONS__] \
[**-g** __GREYLEVELS__] __ARCHIVE_IN__ __OUT__
**DESCRIPTION**
The pictures composing __ARCHIVE_IN__ will be converted to grayscale,
rotated to maximize screen usage, resized using quality filters, posterized
and dithered to save space then archived to __OUT__ (cbz, cbr, cbt or pdf).
**-d** __DIMENSIONS__
Set the target screen dimensions to "WIDTHxHEIGHT" (defaults to
$outdim).
**-g** __GREYLEVELS__
Set the number of levels of grey that the target screen can display
(defaults to $greylevels).
**-h**
Print this notice and exit.
**-n**
Normalize levels.
**-s**
Apply some light sharpening after resizing.
**-w**
Remove JPEG artifacts using waifu2x before resizing.
EOF
exit $1
}
outdim=1404x1872
greylevels=16
denoise=false
sharpen=false
normalize=false
while getopts "d:hg:nsw" OPT
do
case "$OPT" in
d) outdim=$OPTARG;;
g) greylevels=$OPTARG;;
n) normalize=true;;
s) sharpen=true;;
w) denoise=true;;
h) usage 0;;
\?) usage 1;;
esac
done
shift $((OPTIND - 1))
[ $# -ne 2 ] && usage 1
! match "$outdim" '[0-9]+x[0-9]+' && die "$outdim: invalid dimension format"
in=$1
out=$2
requirefile -f "$in"
forbidfile "$out"
match "$out" '.*\.pdf' && requirebin img2pdf natsort
outw=${outdim%x*}
outh=${outdim#*x}
depth=$(pecho "l($greylevels)/l(2)" | bc -l | cut -d. -f1)
workdir=$(mktemp -d)
tempscript=$(pmktemp)
trap 'rm -f -- "$out"; exit 1' HUP INT QUIT ABRT ALRM TERM
trap 'rm -rf -- "$workdir" "$tempscript"' EXIT
# Can't export functions in POSIX sh, so use a temp file
cat <<- EOF >"$tempscript"
#!/bin/sh
w=\$(magick identify -format '%w' "\$1")
h=\$(magick identify -format '%h' "\$1")
out=\${1%.*}.png
upscale=false
rot=
sharp=
norm=
if [ \$w -gt \$h ]
then
rot="-rotate -90"
tmp=\$h
h=\$w
w=\$tmp
fi
if [ \$w -lt $outw ] || [ \$h -lt $outh ]
then
r=\$(echo "w=$outw / \$w; h=$outh / \$h; if (h > w) h else w" | bc)
upscale=true
fi
EOF
if $sharpen
then
cat <<- EOF >>"$tempscript"
sharp="-unsharp 0x3+0.15+0.5"
EOF
fi
if $normalize
then
cat <<- EOF >>"$tempscript"
norm=-normalize
EOF
fi
if $denoise
then
cat <<- EOF >>"$tempscript"
if [ "\${1%.jpeg}" != "\$1" ] || [ "\${1%.jpg}" != "\$1" ]
then
if \$upscale
then
waifu2x-converter-cpp --jobs 1 --log-level 0 --png-compression 0 \
--mode noise-scale --noise-level 1 --scale-ratio \$r \
--input "\$1" --output "\$out"
else
waifu2x-converter-cpp --jobs 1 --log-level 0 --png-compression 0 \
--mode noise --noise-level 1 \
--input "\$1" --output "\$out"
fi
rm -- "\$1"
set -- "\$out"
fi
EOF
else
cat <<- EOF >>"$tempscript"
if \$upscale
then
waifu2x-converter-cpp --jobs 1 --log-level 0 --png-compression 0 \
--mode scale --scale-ratio \$r --input "\$1" --output "\$out"
if [ "\$out" != "\$1" ]
then
rm -- "\$1"
set -- "\$out"
fi
fi
EOF
fi
cat <<EOF >>"$tempscript"
magick convert -limit thread 1 -colorspace Gray \$norm \$rot \
-filter RobidouxSharp -distort Resize "${outw}x${outh}" \$sharp \
-dither FloydSteinberg -colors "$greylevels" -depth "$depth" \
"\$1" "\$out"
if [ "\$out" != "\$1" ]
then
rm -- "\$1"
fi
optipng -o2 -quiet "\$out"
EOF
chmod u+x -- "$tempscript"
archive_extract.sh -d "$workdir" "$in" >/dev/null
find "$workdir" -type f ! \( -iname '*.jpg' -o -iname '*.jpeg' \
-o -iname '*.png' \) -exec rm -- {} +
find "$workdir" -type f \( -iname '*.jpg' -o -iname '*.jpeg' -o \
-iname '*.png' \) | parallel "$tempscript" {}
out=$(preadlinkf "$out")
cd -- "$workdir"
listfiles . | sed 's#^\./##' | \
case "$out" in
*.cbz|*.cbt|*.cbr)
xargsnl archive_create.sh "$out"
;;
*.pdf)
natsort | xargsnl img2pdf >"$out"
;;
*)
die "$out: unknown format"
;;
esac