#!/bin/sh set -eu OUTPUT_DIR="${OUTPUT_DIR:-/scans}" RESOLUTION="${RESOLUTION:-300}" MODE="${MODE:-Color}" SCAN_SOURCE="${SCAN_SOURCE:-Flatbed}" SCAN_WIDTH="${SCAN_WIDTH:-215.9}" SCAN_HEIGHT="${SCAN_HEIGHT:-297}" JPEG_QUALITY="${JPEG_QUALITY:-90}" SCAN_TIMEOUT="${SCAN_TIMEOUT:-300}" PUID="${PUID:-0}" PGID="${PGID:-0}" mkdir -p "$OUTPUT_DIR" DEVICE="${SANE_DEVICE:-}" if [ -z "$DEVICE" ]; then DEVICE="$(scanimage -f '%d%n' 2>/dev/null | sed -n '/^hpaio:/{p;q;}')" fi if [ -z "$DEVICE" ]; then echo "Error: HP scanner not found." >&2 scanimage -L || true exit 2 fi STAMP="$(date '+%Y-%m-%d_%H-%M-%S')" TMP_PNM="$(mktemp "/tmp/${STAMP}.pnm.XXXXXX")" TMP_FIXED="${TMP_PNM}.fixed" OUTPUT_FILE="${OUTPUT_DIR}/${STAMP}.pdf" OUTPUT_COMPLETE=0 cleanup() { rm -f "$TMP_PNM" "$TMP_FIXED" if [ "$OUTPUT_COMPLETE" -ne 1 ]; then rm -f "$OUTPUT_FILE" fi } trap cleanup EXIT HUP INT TERM SUFFIX=1 while [ -e "$OUTPUT_FILE" ]; do OUTPUT_FILE="${OUTPUT_DIR}/${STAMP}_${SUFFIX}.pdf" SUFFIX=$((SUFFIX + 1)) done echo "Scanner: $DEVICE" echo "Source: $SCAN_SOURCE" echo "Resolution: $RESOLUTION dpi" echo "Mode: $MODE" echo "Area: ${SCAN_WIDTH} x ${SCAN_HEIGHT} mm" echo "Output: $OUTPUT_FILE" set +e timeout "$SCAN_TIMEOUT" scanimage \ -d "$DEVICE" \ --source "$SCAN_SOURCE" \ --resolution "$RESOLUTION" \ --mode "$MODE" \ -x "$SCAN_WIDTH" \ -y "$SCAN_HEIGHT" \ --format=pnm \ --output-file="$TMP_PNM" SCAN_STATUS=$? set -e if [ "$SCAN_STATUS" -ne 0 ]; then if [ "$SCAN_STATUS" -eq 124 ] || [ "$SCAN_STATUS" -eq 137 ] || [ "$SCAN_STATUS" -eq 143 ]; then echo "Error: scanner did not respond within ${SCAN_TIMEOUT} seconds." >&2 else echo "Error: scanimage failed with status ${SCAN_STATUS}." >&2 fi exit "$SCAN_STATUS" fi if [ ! -s "$TMP_PNM" ]; then echo "Error: scanner returned an empty PNM file." >&2 exit 3 fi normalize_pnm_height() { MAGIC="$(sed -n '1p' "$TMP_PNM")" COMMENT="$(sed -n '2p' "$TMP_PNM")" DIMENSIONS="$(sed -n '3p' "$TMP_PNM")" if [ "$COMMENT" != '# SANE data follows' ]; then echo "Error: scanner returned an unexpected PNM header." >&2 return 1 fi set -- $DIMENSIONS if [ "$#" -ne 2 ]; then echo "Error: scanner returned invalid PNM dimensions." >&2 return 1 fi WIDTH="$1" HEIGHT="$2" case "${WIDTH}:${HEIGHT}" in *[!0-9:]* | :* | *:) echo "Error: scanner returned invalid PNM dimensions." >&2 return 1 ;; esac case "$MAGIC" in P6) CHANNELS=3 HEADER_LINES=4 ;; P5) CHANNELS=1 HEADER_LINES=4 ;; P4) CHANNELS=0 HEADER_LINES=3 ;; *) echo "Error: scanner returned unsupported PNM type ${MAGIC}." >&2 return 1 ;; esac if [ "$MAGIC" = P4 ]; then ROW_BYTES=$(((WIDTH + 7) / 8)) else MAX_VALUE="$(sed -n '4p' "$TMP_PNM")" case "$MAX_VALUE" in '' | *[!0-9]*) echo "Error: scanner returned an invalid PNM color depth." >&2 return 1 ;; esac if [ "$MAX_VALUE" -gt 255 ]; then SAMPLE_BYTES=2 else SAMPLE_BYTES=1 fi ROW_BYTES=$((WIDTH * CHANNELS * SAMPLE_BYTES)) fi HEADER_BYTES="$(head -n "$HEADER_LINES" "$TMP_PNM" | wc -c)" FILE_BYTES="$(wc -c < "$TMP_PNM")" PAYLOAD_BYTES=$((FILE_BYTES - HEADER_BYTES)) EXPECTED_BYTES=$((ROW_BYTES * HEIGHT)) if [ "$PAYLOAD_BYTES" -eq "$EXPECTED_BYTES" ]; then return 0 fi if [ "$PAYLOAD_BYTES" -lt 0 ] || [ $((PAYLOAD_BYTES % ROW_BYTES)) -ne 0 ]; then echo "Error: scanner returned a truncated PNM row." >&2 return 1 fi ACTUAL_HEIGHT=$((PAYLOAD_BYTES / ROW_BYTES)) MISSING_ROWS=$((HEIGHT - ACTUAL_HEIGHT)) if [ "$MISSING_ROWS" -ne 1 ]; then echo "Error: scanner returned ${ACTUAL_HEIGHT} rows instead of ${HEIGHT}." >&2 return 1 fi echo "Warning: correcting the HPAIO one-row PNM metadata error." >&2 { head -n 2 "$TMP_PNM" printf '%s %s\n' "$WIDTH" "$ACTUAL_HEIGHT" if [ "$HEADER_LINES" -eq 4 ]; then printf '%s\n' "$MAX_VALUE" fi tail -c "+$((HEADER_BYTES + 1))" "$TMP_PNM" } > "$TMP_FIXED" mv "$TMP_FIXED" "$TMP_PNM" } normalize_pnm_height echo "Scan completed." magick \ -density "$RESOLUTION" \ "$TMP_PNM" \ -compress JPEG \ -quality "$JPEG_QUALITY" \ "$OUTPUT_FILE" if [ ! -s "$OUTPUT_FILE" ]; then echo "Error: PDF was not created." >&2 exit 4 fi PDF_MAGIC="$(dd if="$OUTPUT_FILE" bs=5 count=1 2>/dev/null)" if [ "$PDF_MAGIC" != '%PDF-' ]; then echo "Error: output file is not a valid PDF." >&2 exit 5 fi chown "$PUID:$PGID" "$OUTPUT_FILE" 2>/dev/null || true OUTPUT_COMPLETE=1 echo "Saved: $OUTPUT_FILE"