Skip to content

Instantly share code, notes, and snippets.

@yetimdasturchi
Last active June 20, 2026 06:02
Show Gist options
  • Select an option

  • Save yetimdasturchi/063eee362ed0b2dbcbd010ece9e8a90d to your computer and use it in GitHub Desktop.

Select an option

Save yetimdasturchi/063eee362ed0b2dbcbd010ece9e8a90d to your computer and use it in GitHub Desktop.
Create scanned looking PDFs with adjustable DPI/quality support
#!/usr/bin/env bash
set -Eeuo pipefail
SCRIPT_NAME="$(basename "$0")"
INPUT=""
OUTPUT="scanned_output.pdf"
DPI="130"
QUALITY="65"
INSTALL_DEPS="false"
usage() {
cat <<USAGE
Usage:
./$SCRIPT_NAME [options] input.pdf [output.pdf]
Options:
-d, --dpi NUMBER Output resolution. Default: 130
-q, --quality NUMBER JPEG quality from 1 to 100. Default: 65
--install Try to install missing dependencies, then continue
-h, --help Show this help message
Examples:
./$SCRIPT_NAME input.pdf output.pdf
./$SCRIPT_NAME --dpi 130 --quality 65 input.pdf output.pdf
./$SCRIPT_NAME -d 150 -q 72 input.pdf scanned.pdf
./$SCRIPT_NAME --install input.pdf output.pdf
USAGE
}
error() {
echo "Error: $*" >&2
}
info() {
echo "$*"
}
is_number() {
[[ "$1" =~ ^[0-9]+$ ]]
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
-d|--dpi)
[[ $# -ge 2 ]] || { error "Missing value for $1"; exit 1; }
DPI="$2"
shift 2
;;
--dpi=*)
DPI="${1#*=}"
shift
;;
-q|--quality)
[[ $# -ge 2 ]] || { error "Missing value for $1"; exit 1; }
QUALITY="$2"
shift 2
;;
--quality=*)
QUALITY="${1#*=}"
shift
;;
--install)
INSTALL_DEPS="true"
shift
;;
-h|--help)
usage
exit 0
;;
--)
shift
break
;;
-* )
error "Unknown option: $1"
usage
exit 1
;;
*)
if [[ -z "$INPUT" ]]; then
INPUT="$1"
elif [[ "$OUTPUT" == "scanned_output.pdf" ]]; then
OUTPUT="$1"
else
error "Too many arguments: $1"
usage
exit 1
fi
shift
;;
esac
done
while [[ $# -gt 0 ]]; do
if [[ -z "$INPUT" ]]; then
INPUT="$1"
elif [[ "$OUTPUT" == "scanned_output.pdf" ]]; then
OUTPUT="$1"
else
error "Too many arguments: $1"
usage
exit 1
fi
shift
done
}
validate_args() {
if [[ -z "$INPUT" ]]; then
usage
exit 1
fi
if [[ ! -f "$INPUT" ]]; then
error "Input file not found: '$INPUT'"
exit 1
fi
if ! is_number "$DPI" || [[ "$DPI" -lt 72 || "$DPI" -gt 600 ]]; then
error "DPI must be a number between 72 and 600. Current value: $DPI"
exit 1
fi
if ! is_number "$QUALITY" || [[ "$QUALITY" -lt 1 || "$QUALITY" -gt 100 ]]; then
error "Quality must be a number between 1 and 100. Current value: $QUALITY"
exit 1
fi
}
missing_dependencies() {
local missing=()
command -v pdftoppm >/dev/null 2>&1 || missing+=("pdftoppm")
command -v img2pdf >/dev/null 2>&1 || missing+=("img2pdf")
if ! command -v magick >/dev/null 2>&1 && ! command -v convert >/dev/null 2>&1; then
missing+=("imagemagick")
fi
printf '%s\n' "${missing[@]}"
}
install_command_hint() {
cat <<'HINT'
Install commands:
Ubuntu/Debian:
sudo apt update
sudo apt install -y poppler-utils img2pdf imagemagick
Fedora:
sudo dnf install -y poppler-utils img2pdf ImageMagick
Arch Linux:
sudo pacman -S --needed poppler img2pdf imagemagick
macOS with Homebrew:
brew install poppler img2pdf imagemagick
HINT
}
try_install_dependencies() {
info "Trying to install missing dependencies..."
if command -v apt-get >/dev/null 2>&1; then
sudo apt-get update
sudo apt-get install -y poppler-utils img2pdf imagemagick
elif command -v dnf >/dev/null 2>&1; then
sudo dnf install -y poppler-utils img2pdf ImageMagick
elif command -v pacman >/dev/null 2>&1; then
sudo pacman -S --needed poppler img2pdf imagemagick
elif command -v brew >/dev/null 2>&1; then
brew install poppler img2pdf imagemagick
else
error "No supported package manager found. Please install dependencies manually."
install_command_hint
exit 1
fi
}
check_dependencies() {
local missing
missing="$(missing_dependencies | tr '\n' ' ')"
if [[ -z "${missing// }" ]]; then
return 0
fi
error "Required dependency/dependencies are not installed: $missing"
if [[ "$INSTALL_DEPS" == "true" ]]; then
try_install_dependencies
local missing_after
missing_after="$(missing_dependencies | tr '\n' ' ')"
if [[ -n "${missing_after// }" ]]; then
error "Still missing after install attempt: $missing_after"
install_command_hint
exit 1
fi
info "Dependencies installed successfully."
else
install_command_hint
echo ""
echo "Or run this script with --install:"
echo " ./$SCRIPT_NAME --install --dpi $DPI --quality $QUALITY '$INPUT' '$OUTPUT'"
exit 1
fi
}
get_imagemagick_command() {
if command -v magick >/dev/null 2>&1; then
echo "magick"
else
echo "convert"
fi
}
main() {
parse_args "$@"
validate_args
check_dependencies
local im_cmd
im_cmd="$(get_imagemagick_command)"
local workdir pages_dir scanned_dir
workdir="$(mktemp -d)"
pages_dir="$workdir/pages"
scanned_dir="$workdir/scanned"
cleanup() {
rm -rf "$workdir"
}
trap cleanup EXIT
mkdir -p "$pages_dir" "$scanned_dir"
info "Splitting PDF into JPEG pages... DPI=$DPI, QUALITY=$QUALITY"
pdftoppm -r "$DPI" "$INPUT" "$pages_dir/page" -jpeg -jpegopt quality="$QUALITY",progressive=y,optimize=y
info "Applying scan effect..."
local i=0 rotate output_page
shopt -s nullglob
for f in "$pages_dir"/page-*.jpg; do
i=$((i + 1))
if [[ $((i % 2)) -eq 0 ]]; then
rotate="0.35"
else
rotate="-0.25"
fi
output_page="$scanned_dir/$(basename "$f")"
"$im_cmd" "$f" \
-rotate "$rotate" \
-attenuate 0.05 +noise Gaussian \
-blur 0x0.18 \
-brightness-contrast 1x5 \
-strip \
-interlace JPEG \
-sampling-factor 4:2:0 \
-quality "$QUALITY" \
"$output_page"
done
if [[ "$i" -eq 0 ]]; then
error "No pages were generated from the input PDF."
exit 1
fi
info "Creating compressed PDF..."
img2pdf "$scanned_dir"/page-*.jpg -o "$OUTPUT"
info "Done: $OUTPUT"
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment