Adding first version of paperizer.
This commit is contained in:
parent
fe0cc75ef3
commit
1a7d8f0535
147
paperizer/main.py
Executable file
147
paperizer/main.py
Executable file
@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
from base64 import b64encode, b64decode
|
||||
from sys import stderr
|
||||
from textwrap import fill
|
||||
import traceback
|
||||
from os.path import join
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
PAPERIZE_VERSION = "v1.0"
|
||||
|
||||
QR_VERSION = 21
|
||||
|
||||
MAX_LENGTH = {
|
||||
"L": 635,
|
||||
"M": 627,
|
||||
"Q": 455,
|
||||
"H": 369,
|
||||
}
|
||||
|
||||
FILE_HEADER = "---pprzv1:1/{parts}:n:{file_name}---\n"
|
||||
|
||||
PART_HEADER = "---pprzv1:{part}/{parts}---\n"
|
||||
|
||||
PART_TRAILER = "\n---pprz:end---"
|
||||
|
||||
TEMPLATE_HEADER = (
|
||||
"# Paperzied Backup\n"
|
||||
"_Paperize Version {version}_\n\n"
|
||||
"## Backup of file `{file_name}` \n"
|
||||
"To restore this file scan the QR codes and "
|
||||
"paste them into a plain text file (or several).\n\n"
|
||||
"Then use `paperizer file FILE [FILE ...]` to restore the file.\n\n"
|
||||
"Alternatively, if you don't have the _paperizer_ tool handy, you can just "
|
||||
"remove the _paperizer_ tags and newlines and feed the rest through a "
|
||||
"base64 decoding.\n\n"
|
||||
"URL: <https://github.com/faerbit/paperizer>\n\n"
|
||||
)
|
||||
|
||||
FILE_NAME = "qr_part_{i}.png"
|
||||
|
||||
TEMPLATE_PART = (
|
||||
"![File `{file_name}` part {part}/{parts}]({file_path}){{width=95%}}\n\n"
|
||||
)
|
||||
|
||||
def mode_file(file_names):
|
||||
print("Importing...")
|
||||
|
||||
def mode_paper(file_name, ec_level):
|
||||
"""
|
||||
Convert file_name's content into printable pdf using error correction level
|
||||
ec_level.
|
||||
"""
|
||||
# import here to enable mode_file to work with stdlib only
|
||||
import qrcode
|
||||
import pypandoc
|
||||
ERROR_CORRECTION = {
|
||||
"L": qrcode.constants.ERROR_CORRECT_L,
|
||||
"M": qrcode.constants.ERROR_CORRECT_M,
|
||||
"Q": qrcode.constants.ERROR_CORRECT_Q,
|
||||
"H": qrcode.constants.ERROR_CORRECT_H,
|
||||
}
|
||||
# read file
|
||||
file_name = file_name[0]
|
||||
data = ""
|
||||
with open(file_name, "rb") as file:
|
||||
data = file.read()
|
||||
# convert to base64
|
||||
data = b64encode(data).decode("utf-8")
|
||||
# split data
|
||||
parts = len(data)//MAX_LENGTH[ec_level]
|
||||
split_data = prepare_data(data, parts, MAX_LENGTH[ec_level], file_name)
|
||||
if (len(split_data) != parts):
|
||||
split_data = prepare_data(data, len(split_data),
|
||||
MAX_LENGTH[ec_level], file_name)
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
for i, part in enumerate(split_data, 1):
|
||||
img = qrcode.make(part,
|
||||
error_correction=ERROR_CORRECTION[ec_level])
|
||||
img.save(join(tmpdir, FILE_NAME.format(i=i)))
|
||||
markdown = TEMPLATE_HEADER.format(version=PAPERIZE_VERSION,
|
||||
file_name=file_name)
|
||||
for i in range(1, len(split_data) + 1):
|
||||
markdown += TEMPLATE_PART.format(part=i, parts=len(split_data),
|
||||
file_name=file_name,
|
||||
file_path=join(tmpdir, FILE_NAME.format(i=i)))
|
||||
pdf = pypandoc.convert_text(markdown, "pdf", format="md",
|
||||
outputfile=f"{file_name}_paperized.pdf",
|
||||
extra_args=["-V", "geometry:margin=1.5cm"])
|
||||
|
||||
def prepare_data(data, no_parts, part_length, file_name):
|
||||
"""
|
||||
Split data into no_parts chunks, while also adding the headers and
|
||||
trailers.
|
||||
"""
|
||||
ret = []
|
||||
file_header = FILE_HEADER.format(parts=no_parts, file_name=file_name)
|
||||
first_part_length = part_length - len(file_header) - len(PART_TRAILER)
|
||||
first_part = fill(data, 80)[:first_part_length]
|
||||
ret.append(file_header + first_part + PART_TRAILER)
|
||||
data = fill(data, 80)[first_part_length:]
|
||||
i = 2
|
||||
while data:
|
||||
data = data.replace("\n", "")
|
||||
part_header = PART_HEADER.format(part=i, parts=no_parts)
|
||||
length = part_length - len(part_header) - len(PART_TRAILER)
|
||||
part = fill(data, 80)[:length]
|
||||
ret.append(part_header + part + PART_TRAILER)
|
||||
data = fill(data, 80)[length:]
|
||||
i += 1
|
||||
return ret
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description =
|
||||
"Convert binary file to printable QR codes and back."
|
||||
"Will output a printable pdf file",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument("mode", metavar="MODE", help=
|
||||
"Either \"paper\" or \"file\". Can be shortened. \"paper\" "
|
||||
"produces QR codes and \"file\" produces the original file.")
|
||||
parser.add_argument("file", metavar="FILE", nargs="+", help=
|
||||
"Either the file which should be exported "
|
||||
"or the plain text input file(s) for import." )
|
||||
parser.add_argument("-l", "--level", default="M", help=
|
||||
"Error correction level for QR codes. "
|
||||
"Accepts L (7%%), M (15%%), Q (25%%), and H (30%%).",
|
||||
choices=["L", "M", "Q", "H",], type=str.upper)
|
||||
args = parser.parse_args()
|
||||
if not ("paper".find(args.mode.lower()) == 0 or
|
||||
"file".find(args.mode.lower()) == 0):
|
||||
print("Could not recognize MODE argument. Please use \"paper\" "
|
||||
"or \"file\" or a shortened version of these.", file=stderr)
|
||||
exit(1)
|
||||
|
||||
if ("file".find(args.mode.lower()) == 0):
|
||||
mode_file(args.file)
|
||||
else:
|
||||
if len(args.file) > 1:
|
||||
print("Only one file for \"paper\" mode supported.", file=stderr)
|
||||
exit(1)
|
||||
mode_paper(args.file, args.level)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
Loading…
Reference in New Issue
Block a user