211 lines
4.4 KiB
Bash
Executable File
211 lines
4.4 KiB
Bash
Executable File
#!/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"
|