From b1f9aa7fc1cd3de83bdc89e440e08d2a0ca37d44 Mon Sep 17 00:00:00 2001 From: Jose Riha Date: Mon, 16 Dec 2019 09:50:13 +0100 Subject: [PATCH] Add cli arguments parsing to allow output customization This also means that QR code positions are no more hardcoded. --- paperbackup.py | 154 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 126 insertions(+), 28 deletions(-) diff --git a/paperbackup.py b/paperbackup.py index a99fe38..0c69234 100755 --- a/paperbackup.py +++ b/paperbackup.py @@ -28,33 +28,18 @@ # import os +import logging import re import sys import hashlib import subprocess import qrencode +import argparse from tempfile import mkstemp from datetime import datetime from PIL import Image from pyx import * -# constants for the size and layout of the barcodes on page -max_bytes_in_barcode = 140 -barcodes_per_page = 6 -barcode_height = 8 -barcode_x_positions = [1.5, 11, 1.5, 11, 1.5, 11] -barcode_y_positions = [18.7, 18.7, 10, 10, 1.2, 1.2] -text_x_offset = 0 -text_y_offset = 8.2 - -plaintext_maxlinechars = 73 - -# the paperformat to use, activate the one you want -paperformat_obj = document.paperformat.A4 -paperformat_str = "A4" -# paperformat_obj=document.paperformat.Letter -# paperformat_str="Letter" - def create_barcode(chunkdata): version, size, im = qrencode.encode(chunkdata, @@ -68,17 +53,110 @@ def finish_page(pdf, canv, pageno): pdf.append(document.page(canv, paperformat=paperformat_obj, fittosize=0, centered=0)) + # main code -if len(sys.argv) != 2: - raise RuntimeError('Usage {} FILENAME.asc'.format(sys.argv[0])) +parser = argparse.ArgumentParser(description='Generate a PDF with barcodes for a given file.') +parser.add_argument('-c', dest='columns', nargs=1, default=[4], type=int, + help='number of columns per page (default: 4)') +parser.add_argument('-d', dest='debug', action='store_true', + help='debug output') +parser.add_argument('-g', dest='gap', nargs=1, default=[2], type=int, + help='minimum gap (%%, default: 2)') +parser.add_argument('-r', dest='rows', nargs=1, default=[5], type=int, + help='number of rows per page (default: 5)') +parser.add_argument('-s', dest='paper_size', nargs=1, default=['a4'], + help='paper size: a4 or letter (default: a4)') +parser.add_argument('input_file', nargs=1, + help='file to process (perhaps base64-encoded)') + +args = parser.parse_args() + +if not args.input_file: + parser.print_help() + sys.exit() + +input_file = args.input_file[0] + +if args.debug: + logging.basicConfig(level=logging.DEBUG) +else: + logging.basicConfig(level=logging.CRITICAL) -input_path = sys.argv[1] -if not os.path.isfile(input_path): - raise RuntimeError('File {} not found'.format(input_path)) -just_filename = os.path.basename(input_path) +# constants for the size and layout of the barcodes on page +max_bytes_in_barcode = 140 -with open(input_path) as inputfile: +# page margins +top_margin = 2.2 +right_margin = 1.2 +left_margin = 1.5 +bottom_margin = 1.5 + +paper_size = args.paper_size[0].lower() + +if paper_size == 'a4': + paperformat_obj = document.paperformat.A4 + paperformat_str = "A4" + # paper size in cm + paper_width = 21 + paper_height = 29.7 +else: + paperformat_obj=document.paperformat.Letter + paperformat_str="Letter" + # paper size in cm + paper_width = 21.6 + paper_height = 27.9 + +logging.info('Paper size: {0}'.format(paper_size)) + +# number of barcode rows/columns per page (4/5 by default) +barcode_cols = args.columns[0] +barcode_rows = args.rows[0] + +cell_width = (paper_width - left_margin - right_margin) / barcode_cols +cell_height = (paper_height - top_margin - bottom_margin) / barcode_rows + +# fix "X" +logging.info('Cell dimensions: {0:.2f}×{1:.2f} cm'.format(cell_width, cell_height)) + +gap_perc = args.gap[0] + +if cell_width <= cell_height: + horizontal_gap = gap_perc * cell_width / 100 + barcode_height = cell_width - horizontal_gap + vertical_gap = cell_height - barcode_height +else: + vertical_gap = gap_perc * cell_height / 100 + barcode_height = cell_height - vertical_gap + horizontal_gap = cell_width - barcode_height + +logging.info('Horizontal gap: {0:.2f} cm'.format(horizontal_gap)) +logging.info('Vertical gap: {0:.2f} cm'.format(vertical_gap)) +logging.info('Barcode height/width: {0:.2f} cm'.format(barcode_height)) + + +barcode_x_positions = [left_margin + (x * (horizontal_gap + barcode_height)) for x in range(barcode_cols)] * barcode_rows +barcode_y_positions = list() +[barcode_y_positions.extend(barcode_cols * [bottom_margin + (x * (vertical_gap + barcode_height))]) for x in range(barcode_rows)] +barcode_y_positions.reverse() +barcodes_per_page = barcode_rows * barcode_cols +text_x_offset = 0 +text_y_offset = barcode_height + 0.2 +logging.info('Barcode x positions: {0}'.format(barcode_x_positions)) +logging.info('Barcode y positions: {0}'.format(barcode_y_positions)) + +# align to top margin +content_top = max(barcode_y_positions) + text_y_offset +header_content_gap = paper_height - top_margin - content_top +barcode_y_positions = [x + header_content_gap for x in barcode_y_positions] + +plaintext_maxlinechars = 73 + +if not os.path.isfile(input_file): + raise RuntimeError('File {} not found'.format(input_file)) +just_filename = os.path.basename(input_file) + +with open(input_file) as inputfile: ascdata = inputfile.read() # only allow some harmless characters @@ -87,7 +165,8 @@ def finish_page(pdf, canv, pageno): allowedchars = re.compile(r"^[A-Za-z0-9/=+:., #@!()\n-]*") allowedmatch = allowedchars.match(ascdata) if allowedmatch.group() != ascdata: - raise RuntimeError('Illegal char found at %d >%s<' + raise RuntimeError('Illegal char found at %d >%s<.\n' + 'Maybe you want to base64-encode the file first?' % (len(allowedmatch.group()), ascdata[len(allowedmatch.group())])) @@ -99,13 +178,18 @@ def finish_page(pdf, canv, pageno): for char in list(ascdata): if len(chunkdata)+1 > max_bytes_in_barcode: # chunk is full -> create barcode from it + logging.debug('Creating barcode no {0}'.format(len(barcode_blocks) + 1)) + logging.debug('Chunkdata: {0}'.format(chunkdata)) barcode_blocks.append(create_barcode(chunkdata)) chunkdata = "^" + str(len(barcode_blocks)+1) + " " chunkdata += char # handle the last, non filled chunk too -barcode_blocks.append(create_barcode(chunkdata)) +if len(chunkdata) > len(str(len(barcode_blocks))) + 2: + logging.debug('Creating barcode no {0}'.format(len(barcode_blocks) + 1)) + logging.debug('Chunkdata: {0}'.format(chunkdata)) + barcode_blocks.append(create_barcode(chunkdata)) # init PyX unit.set(defaultunit="cm") @@ -114,6 +198,18 @@ def finish_page(pdf, canv, pageno): # place barcodes on pages pgno = 0 # page number ppos = 0 # position id on page + +if len(just_filename) > 19: + font_size = text.size.tiny +elif len(just_filename) > 15: + font_size = text.size.small +elif len(just_filename) > 10: + font_size = text.size.normal +else: + font_size = text.size.Large + +logging.debug('Font size for QR labels: {0}'.format(font_size.size)) + c = canvas.canvas() for bc in range(len(barcode_blocks)): # page full? @@ -126,7 +222,8 @@ def finish_page(pdf, canv, pageno): c.text(barcode_x_positions[ppos] + text_x_offset, barcode_y_positions[ppos] + text_y_offset, "%s (%i/%i)" % (text.escapestring(just_filename), - bc+1, len(barcode_blocks))) + bc+1, len(barcode_blocks)), + [font_size]) c.insert(bitmap.bitmap(barcode_x_positions[ppos], barcode_y_positions[ppos], barcode_blocks[bc], height=barcode_height)) @@ -141,7 +238,7 @@ def finish_page(pdf, canv, pageno): # prepare plain text output fd, temp_text_path = mkstemp('.ps', 'text_', '.') -input_file_modification = datetime.fromtimestamp(os.path.getmtime(input_path)).strftime("%Y-%m-%d %H:%M:%S") +input_file_modification = datetime.fromtimestamp(os.path.getmtime(input_file)).strftime("%Y-%m-%d %H:%M:%S") # split lines on plaintext_maxlinechars - ( checksum_size + separator size) splitat=plaintext_maxlinechars - 8 @@ -193,6 +290,7 @@ def finish_page(pdf, canv, pageno): # use "enscript" to create postscript with the plaintext p = subprocess.Popen( ["enscript", "-p"+temp_text_path, "-f", "Courier12", + "-t" + just_filename, "-M" + paperformat_str, "--header", just_filename + "|" + input_file_modification + "|Page $%"], stdout=subprocess.PIPE, stdin=subprocess.PIPE)