home · contact · privacy
Bookmaker: improve error handling.
authorChristian Heller <c.heller@plomlompom.de>
Sun, 24 Sep 2023 03:49:24 +0000 (05:49 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Sun, 24 Sep 2023 03:49:24 +0000 (05:49 +0200)
bookmaker.py

index 646877a7a15c8cd71a2f02ce134def7dac237a2a..8d0f0c4e7a0a7e467ae41f3c63c81b00d9d2e04f 100755 (executable)
@@ -1,16 +1,25 @@
 #!/usr/bin/env python3
 #!/usr/bin/env python3
-import pypdf
 import argparse
 import io
 import os
 import argparse
 import io
 import os
-from reportlab.lib.pagesizes import A4
+def fail_with_msg(msg):
+    print("ERROR:", msg)
+    exit(1)
+try:
+    import pypdf
+except ImportError:
+    fail_with_msg("Can't run without pypdf installed.")
+try:
+    from reportlab.lib.pagesizes import A4
+except ImportError:
+    fail_with_msg("Can't run without reportlab installed.")
+
 a4_width, a4_height = A4
 points_per_cm = 10 * 72 / 25.4
 cut_depth = 1.95 * points_per_cm
 cut_width = 1.05 * points_per_cm
 middle_point_depth = 0.4 * points_per_cm
 spine_limit = 1 * points_per_cm
 a4_width, a4_height = A4
 points_per_cm = 10 * 72 / 25.4
 cut_depth = 1.95 * points_per_cm
 cut_width = 1.05 * points_per_cm
 middle_point_depth = 0.4 * points_per_cm
 spine_limit = 1 * points_per_cm
-
 desc = """bookmaker.py is a helper for optimizing PDFs of books for the production of small self-printed, self-bound physical books  To this goal it offers various PDF manipulation options potentially that can also be used indepéndently and for other purposes.
 """
 epilogue = """
 desc = """bookmaker.py is a helper for optimizing PDFs of books for the production of small self-printed, self-bound physical books  To this goal it offers various PDF manipulation options potentially that can also be used indepéndently and for other purposes.
 """
 epilogue = """
@@ -69,7 +78,6 @@ To facilitate this layout, --nup4 also pads the input PDF pages to a total numbe
 (To turn this page into a tiny 8-page book, cut the paper in two on its horizontal middle line.  Fold the two halves by their vertical middle lines, with pages 3-2 and 7-6 on the folds' insides.  This creates two 4-page books of pages 1-4 and pages 5-8.  Fold them both closed and (counter-intuitively) put the book of pages 5-8 on top of the other one (creating a temporary page order of 5,6,7,8,1,2,3,4).  A binding cut stencil should be visible on the top left of this stack – cut it out (with all pages folded together) to add the same inner-margin upper cut to each page.  Turn around your 8-pages stack to find the mirror image of aforementioned stencil on the stack's back's bottom, and cut it out too.  Each page now has binding cuts on top and bottom of its inner margins.  Swap the order of both books (back to the final page order of 1,2,3,4,5,6,7,8), and you now have an 8-pages book that can be "bound" in its binding cuts through a rubber band or the like.  Repeat with the next 8-pages double-page, et cetera.  (Actually, with just 8 pages, the paper may curl under the pressure of a rubber band – but go up to 32 pages or so, and the result will become quite stable.)
 """
 
 (To turn this page into a tiny 8-page book, cut the paper in two on its horizontal middle line.  Fold the two halves by their vertical middle lines, with pages 3-2 and 7-6 on the folds' insides.  This creates two 4-page books of pages 1-4 and pages 5-8.  Fold them both closed and (counter-intuitively) put the book of pages 5-8 on top of the other one (creating a temporary page order of 5,6,7,8,1,2,3,4).  A binding cut stencil should be visible on the top left of this stack – cut it out (with all pages folded together) to add the same inner-margin upper cut to each page.  Turn around your 8-pages stack to find the mirror image of aforementioned stencil on the stack's back's bottom, and cut it out too.  Each page now has binding cuts on top and bottom of its inner margins.  Swap the order of both books (back to the final page order of 1,2,3,4,5,6,7,8), and you now have an 8-pages book that can be "bound" in its binding cuts through a rubber band or the like.  Repeat with the next 8-pages double-page, et cetera.  (Actually, with just 8 pages, the paper may curl under the pressure of a rubber band – but go up to 32 pages or so, and the result will become quite stable.)
 """
 
-# parser = argparse.ArgumentParser(description="build print-ready book PDF")
 parser = argparse.ArgumentParser(description=desc, epilog=epilogue, formatter_class=argparse.RawDescriptionHelpFormatter)
 parser._optionals.title = "OPTIONS"
 parser.add_argument("-i", "--input_file", action="append", required=True, help="input PDF file")
 parser = argparse.ArgumentParser(description=desc, epilog=epilogue, formatter_class=argparse.RawDescriptionHelpFormatter)
 parser._optionals.title = "OPTIONS"
 parser.add_argument("-i", "--input_file", action="append", required=True, help="input PDF file")
@@ -87,10 +95,10 @@ args = parser.parse_args()
 def validate_page_range(p_string, err_msg_prefix):
     err_msg = "%s: invalid page range string: %s" % (err_msg_prefix, p_string)
     if '-' not in p_string:
 def validate_page_range(p_string, err_msg_prefix):
     err_msg = "%s: invalid page range string: %s" % (err_msg_prefix, p_string)
     if '-' not in p_string:
-        raise ValueError("%s: page range string lacks '-': %s" % (err_msg_prefix, p_string))
+        fail_with_msg("%s: page range string lacks '-': %s" % (err_msg_prefix, p_string))
     tokens = p_string.split("-")
     if len(tokens) > 2:
     tokens = p_string.split("-")
     if len(tokens) > 2:
-        raise ValueError("%s: page range string has too many '-': %s" % (err_msg_prefix, p_string))
+        fail_with_msg("%s: page range string has too many '-': %s" % (err_msg_prefix, p_string))
     for i, token in enumerate(tokens):
         if token == "":
             continue
     for i, token in enumerate(tokens):
         if token == "":
             continue
@@ -101,9 +109,9 @@ def validate_page_range(p_string, err_msg_prefix):
         try:
             int(token)
         except:
         try:
             int(token)
         except:
-            raise ValueError("%s: page range string carries values that are neither integer, nor 'start', nor 'end': %s" % (err_msg_prefix, p_string))
+            fail_with_msg("%s: page range string carries values that are neither integer, nor 'start', nor 'end': %s" % (err_msg_prefix, p_string))
         if int(token) < 1:
         if int(token) < 1:
-            raise ValueError("%s: page range string may not carry page numbers <1: %s" % (err_msg_prefix, p_string))
+            fail_with_msg("%s: page range string may not carry page numbers <1: %s" % (err_msg_prefix, p_string))
     start = -1
     end = -1
     try:
     start = -1
     end = -1
     try:
@@ -112,25 +120,26 @@ def validate_page_range(p_string, err_msg_prefix):
     except:
         pass
     if start > 0 and end > 0 and start > end:
     except:
         pass
     if start > 0 and end > 0 and start > end:
-        raise ValueError("%s: page range starts higher than it ends: %s" % (err_msg_prefix, p_string))
+        fail_with_msg("%s: page range starts higher than it ends: %s" % (err_msg_prefix, p_string))
+
 for filename in args.input_file:
     if not os.path.isfile(filename):
 for filename in args.input_file:
     if not os.path.isfile(filename):
-        raise ValueError("-i: %s is not a file" % filename)
+        fail_with_msg("-i: %s is not a file" % filename)
     try:
         with open(filename, 'rb') as file:
             pypdf.PdfReader(file)
     except pypdf.errors.PdfStreamError:
     try:
         with open(filename, 'rb') as file:
             pypdf.PdfReader(file)
     except pypdf.errors.PdfStreamError:
-        raise ValueError("-i: cannot interpret %s as PDF file" % filename)
+        fail_with_msg("-i: cannot interpret %s as PDF file" % filename)
 if args.page_range:
     for p_string in args.page_range:
         validate_page_range(p_string, "-p")
     if len(args.page_range) > len(args.input_file):
 if args.page_range:
     for p_string in args.page_range:
         validate_page_range(p_string, "-p")
     if len(args.page_range) > len(args.input_file):
-        raise ValueError("more -p arguments than -i arguments")
+        fail_with_msg("more -p arguments than -i arguments")
 if args.crops:
     for c_string in args.crops:
         initial_split = c_string.split(':')
         if len(initial_split) > 2:
 if args.crops:
     for c_string in args.crops:
         initial_split = c_string.split(':')
         if len(initial_split) > 2:
-            raise ValueError("-c: cropping string has multiple ':': %s" % c_string)
+            fail_with_msg("-c: cropping string has multiple ':': %s" % c_string)
         if len(initial_split) > 1:
             validate_page_range(initial_split[0], "-c")
             crops = initial_split[1].split(",")
         if len(initial_split) > 1:
             validate_page_range(initial_split[0], "-c")
             crops = initial_split[1].split(",")
@@ -138,24 +147,24 @@ if args.crops:
         else:
             crops = initial_split[0].split(",")
         if len(crops) != 4:
         else:
             crops = initial_split[0].split(",")
         if len(crops) != 4:
-            raise ValueError("-c: cropping should contain three ',': %s" % c_string)
+            fail_with_msg("-c: cropping should contain three ',': %s" % c_string)
         for crop in crops:
             try:
                 float(crop)
             except:
         for crop in crops:
             try:
                 float(crop)
             except:
-                raise ValueError("-c: non-number crop in %s" % c_string)
+                fail_with_msg("-c: non-number crop in %s" % c_string)
 if args.rotate_page:
     for r in args.rotate_page:
         try:
             int(r)
         except:
 if args.rotate_page:
     for r in args.rotate_page:
         try:
             int(r)
         except:
-            raise ValueError("-r: non-integer value: %s" % r)
+            fail_with_msg("-r: non-integer value: %s" % r)
         if r < 1:
         if r < 1:
-            raise ValueError("-r: value must not be <1: %s" % r)
+            fail_with_msg("-r: value must not be <1: %s" % r)
 try:
     float(args.print_margin)
 except:
 try:
     float(args.print_margin)
 except:
-    raise ValueError("-m: non-float value: %s" % arg.print_margin)
+    fail_with_msg("-m: non-float value: %s" % arg.print_margin)
 
 # select pages from input files
 def parse_page_range(range_string, pages):
 
 # select pages from input files
 def parse_page_range(range_string, pages):
@@ -168,6 +177,7 @@ def parse_page_range(range_string, pages):
         if not (len(end) == 0 or end == "end"):
             end_page = int(end)
     return start_page, end_page
         if not (len(end) == 0 or end == "end"):
             end_page = int(end)
     return start_page, end_page
+
 pages_to_add = []
 opened_files = []
 new_page_num = 0
 pages_to_add = []
 opened_files = []
 new_page_num = 0
@@ -180,7 +190,7 @@ for i, input_file in enumerate(args.input_file):
         range_string = args.page_range[i]
     start_page, end_page = parse_page_range(range_string, reader.pages)
     if end_page > len(reader.pages):  # no need to test start_page cause start_page > end_page is checked above
         range_string = args.page_range[i]
     start_page, end_page = parse_page_range(range_string, reader.pages)
     if end_page > len(reader.pages):  # no need to test start_page cause start_page > end_page is checked above
-        raise ValueError("-p: page range goes beyond pages of input file: %s" % range_string)
+        fail_with_msg("-p: page range goes beyond pages of input file: %s" % range_string)
     for old_page_num in range(start_page, end_page):
         new_page_num += 1
         page = reader.pages[old_page_num]
     for old_page_num in range(start_page, end_page):
         new_page_num += 1
         page = reader.pages[old_page_num]
@@ -194,11 +204,11 @@ if args.crops:
         if len(initial_split) > 1:
             start, end = parse_page_range(initial_split[0], pages_to_add)
             if end > len(pages_to_add):
         if len(initial_split) > 1:
             start, end = parse_page_range(initial_split[0], pages_to_add)
             if end > len(pages_to_add):
-                 raise ValueError("-c: page range goes beyond number of pages we're building: %s" % initial_split[0])
+                 fail_with_msg("-c: page range goes beyond number of pages we're building: %s" % initial_split[0])
 if args.rotate_page:
     for r in args.rotate_page:
         if r > len(pages_to_add):
 if args.rotate_page:
     for r in args.rotate_page:
         if r > len(pages_to_add):
-             raise ValueError("-r: page number beyond number of pages we're building: %d" % r)
+             fail_with_msg("-r: page number beyond number of pages we're building: %d" % r)
 
 # rotate page canvas
 if args.rotate_page:
 
 # rotate page canvas
 if args.rotate_page:
@@ -256,7 +266,7 @@ if args.crops:
         zoom_horizontal = a4_width / (a4_width - crop_left - crop_right)
         zoom_vertical = a4_height / (a4_height - crop_bottom - crop_top)
         if (zoom_horizontal > 1 and zoom_vertical < 1) or (zoom_horizontal < 1 and zoom_vertical > 1):
         zoom_horizontal = a4_width / (a4_width - crop_left - crop_right)
         zoom_vertical = a4_height / (a4_height - crop_bottom - crop_top)
         if (zoom_horizontal > 1 and zoom_vertical < 1) or (zoom_horizontal < 1 and zoom_vertical > 1):
-            raise ValueError("crops would create opposing zoom directions")
+            fail_with_msg("crops would create opposing zoom directions")
         elif zoom_horizontal + zoom_vertical > 2:
             zoom = min(zoom_horizontal, zoom_vertical)
         else:
         elif zoom_horizontal + zoom_vertical > 2:
             zoom = min(zoom_horizontal, zoom_vertical)
         else: