Skip to content

Instantly share code, notes, and snippets.

@ethan605
Last active April 5, 2024 15:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ethan605/b6888f3c0e12e4f8168baf97f2164750 to your computer and use it in GitHub Desktop.
Save ethan605/b6888f3c0e12e4f8168baf97f2164750 to your computer and use it in GitHub Desktop.
qrgpg - encode/decode ASCII armoured file to/from QRCode images
#!/usr/bin/env bash
set -Eeuo pipefail
readonly RED=$(tput setaf 1)
readonly GREEN=$(tput setaf 2)
readonly YELLOW=$(tput setaf 3)
readonly NORMAL=$(tput sgr0)
readonly COMMAND="qrgpg"
SUB_COMMAND=""
INPUT_FILES=()
TOTAL_BLOCKS=7
OUTPUT_FILE="qrgpg.asc"
OUTPUT_PREFIX="qrgpg"
FONT_FILE=""
function print_help_message() {
local error_message=${1-}
if [[ ! -z $error_message ]]; then
printf "${RED}Error:${NORMAL} $error_message\n\n" >&2
fi
printf "$COMMAND - encode/decode ASCII armoured file to/from QRCode images
Usage:
$COMMAND encode [options] [flags] file
or $COMMAND decode [options] [flags] file...
Example:
$COMMAND encode -b 8 -p gpg private_key.asc
$COMMAND encode -f /path/to/custom/font private_key.asc
$COMMAND decode -o gpg.asc *.png
Available options for 'encode' mode:
-b, --blocks <string> Number of blocks to be encoded to, default to '7'.
-f, --font-file <file> Path to font file used for output image embedded caption.
-p, --output-prefix <string> String to be prepended to output image files, default to 'qrgpg'.
Available options for 'decode' mode:
-o, --output-file <string> Output file name, default to 'qrgpg.asc'.
Available flags
-h, --help Print this message
"
}
function parse_option_arg() {
if [[ -n "${2-}" ]] && [[ ${2:0:1} != "-" ]]; then
echo $2
else
print_help_message "Argument for $1 is missing"
exit 1
fi
}
function parse_args() {
while (( "$#" )); do
case "$1" in
encode|decode)
SUB_COMMAND=$1
shift
;;
-b|--blocks)
TOTAL_BLOCKS=$(parse_option_arg "$@")
shift 2
;;
-f|--font-file)
FONT_FILE=$(parse_option_arg "$@")
shift 2
;;
-p|--output-prefix)
OUTPUT_PREFIX=$(parse_option_arg "$@")
shift 2
;;
-o|--output-file)
OUTPUT_FILE=$(parse_option_arg "$@")
shift 2
;;
-h|--help)
print_help_message
exit 1
;;
-*|--*=) # unsupported args
print_help_message "Unsupported argument $1"
exit 1
;;
*) # preserve positional args
INPUT_FILES+=($1)
shift
;;
esac
done
if [[ $SUB_COMMAND != "encode" ]] && [[ $SUB_COMMAND != "decode" ]]; then
print_help_message "Unsupported sub-command"
exit 1
fi
if [[ ${#INPUT_FILES[@]} == 0 ]]; then
if [[ $SUB_COMMAND == "encode" ]]; then
print_help_message "Missing input file"
fi
if [[ $SUB_COMMAND == "decode" ]]; then
print_help_message "Missing files list"
fi
exit 1
fi
}
function zero_padding() {
printf "%02d" $1
}
function print_check_result() {
local result=${1:-""}
[[ ! -z $result ]] && echo "${GREEN}✔︎${NORMAL}" || echo "${RED}✗${NORMAL}"
}
function check_dependencies() {
local qrencode_check=$(command -v qrencode)
local zbarimg_check=$(command -v zbarimg)
local imagemagick_check=$(command -v convert)
if [[ -z $qrencode_check ||-z $zbarimg_check ||-z $imagemagick_check ]]; then
printf "${RED}Error:${NORMAL} following dependencies are required:
$(print_check_result $qrencode_check) qrencode (https://fukuchi.org/works/qrencode)
$(print_check_result $zbarimg_check) zbarimg (https://github.com/mchehab/zbar)
$(print_check_result $imagemagick_check) imagemagick (https://imagemagick.org)
"
exit 1
fi
}
function generate_qr() {
local block_order=$(zero_padding $1)
local blocks_count=$(zero_padding $2)
local block_data=$3
local prefix=$4
local out_file="${prefix}_${block_order}.png"
echo -e "$block_data" |
qrencode \
--dpi=300 \
--level=H \
--size=4 \
--output="$out_file"
if [[ ! -z $FONT_FILE ]]; then
convert "$out_file" \
-gravity South \
-splice 0x15 \
-font "$FONT_FILE" \
-annotate +0+10 "$prefix ${block_order}/$blocks_count" "$out_file"
else
convert "$out_file" \
-gravity South \
-splice 0x15 \
-annotate +0+10 "$prefix ${block_order}/$blocks_count" "$out_file"
fi
printf "${GREEN}Block $block_order${NORMAL} successfully encoded into image $out_file\n"
}
function calc_lines_per_block() {
local input_file=$1
local blocks_count=$2
local total_lines=$(wc -l < "$input_file" | grep --only-matching --extended-regexp "[0-9]+")
local lines_per_block=$(expr $total_lines / $blocks_count)
echo $lines_per_block
}
function encode() {
local input_file=${INPUT_FILES[0]}
local lines_per_block=$(calc_lines_per_block "$input_file" $TOTAL_BLOCKS)
local block_data=""
local block_order=1
local line_number=1
while IFS= read -r line_data
do
if [[ $line_number -eq 1 ]]; then
block_data=$line_data
else
block_data+="\n$line_data"
fi
let line_number++
if [[ $line_number -gt $lines_per_block && $block_order -lt $TOTAL_BLOCKS ]]; then
generate_qr $block_order $TOTAL_BLOCKS "$block_data" "$OUTPUT_PREFIX"
let block_order++
let line_number=1
block_data=""
fi
done < $input_file
generate_qr $block_order $TOTAL_BLOCKS "$block_data" "$OUTPUT_PREFIX"
printf "\n${GREEN}Encoding done!${NORMAL}\n"
}
function decode() {
local data=""
for input_file in ${INPUT_FILES[@]}; do
printf "${GREEN}Decoding${NORMAL} $input_file\n"
local decoded_data=$(zbarimg --quiet --raw "$input_file")
data+="$decoded_data\n"
done
echo -e "$data" > "$OUTPUT_FILE"
printf "\n${GREEN}Decoding done!${NORMAL} Data written to $OUTPUT_FILE\n"
}
function main() {
check_dependencies
parse_args "$@"
case "$SUB_COMMAND" in
encode) encode ;;
decode) decode ;;
esac
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment