From 50efb4fb13ab442523280afdb17a79abe9b1a87b Mon Sep 17 00:00:00 2001 From: NilsLeo Date: Sun, 19 Jul 2026 22:58:17 +0200 Subject: [PATCH] Skip unreadable pages during PDF extraction instead of failing When PyMuPDF raised while rendering or extracting a single page, the exception propagated out of the worker Pool and often could not be pickled (SwigPyObject), so the whole job died with an opaque pickling TypeError instead of the real cause. Catch per-page errors, skip the offending page and continue, so a PDF with a few bad pages still converts. --- kindlecomicconverter/comic2ebook.py | 62 +++++++++++++++++------------ 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 0a64595d..c6c512b6 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -803,16 +803,22 @@ def render_page(vector): seg_to = min(seg_from + seg_size, num_pages) # last page number for i in range(seg_from, seg_to): # work through our page segment - page = doc[i] - if not pdf_width or page.rect.width > page.rect.height: - zoom = target_height / page.rect.height - else: - zoom = target_width / page.rect.width - mat = pymupdf.Matrix(zoom, zoom) - # TODO: decide colorspace earlier so later color check is cheaper. - # This is actually pretty hard when you have to deal with color vector text - pix = page.get_pixmap(matrix=mat, colorspace='RGB', alpha=False) - pix.save(os.path.join(output_dir, "p-%i.png" % i)) + try: + page = doc[i] + if not pdf_width or page.rect.width > page.rect.height: + zoom = target_height / page.rect.height + else: + zoom = target_width / page.rect.width + mat = pymupdf.Matrix(zoom, zoom) + # TODO: decide colorspace earlier so later color check is cheaper. + # This is actually pretty hard when you have to deal with color vector text + pix = page.get_pixmap(matrix=mat, colorspace='RGB', alpha=False) + pix.save(os.path.join(output_dir, "p-%i.png" % i)) + except Exception as e: + # A single unrenderable page must not abort the whole PDF. Skipping it + # also prevents an unpicklable PyMuPDF error from crossing the Pool + # boundary and masking the real cause with a pickling TypeError. + print("WARNING: Skipping PDF page %i: %s" % (i, e)) print("Processed page numbers %i through %i" % (seg_from, seg_to - 1)) @@ -852,22 +858,28 @@ def extract_page(vector): for i in range(seg_from, seg_to): # work through our page segment output_path = os.path.join(output_dir, "p-%i.png" % i) - page = doc.load_page(i) - image_list = page.get_images() - if len(image_list) > 1: - raise UserWarning("mupdf_pdf_extract_page_image() function can be used only with single image pages.") - if not image_list: - continue - else: - xref = image_list[0][0] - d = doc.extract_image(xref) - if d['cs-name'] == 'DeviceCMYK': - pix = pymupdf.Pixmap(doc, xref) - pix = pymupdf.Pixmap(pymupdf.csRGB, pix) - pix.save(output_path) + try: + page = doc.load_page(i) + image_list = page.get_images() + if len(image_list) > 1: + raise UserWarning("mupdf_pdf_extract_page_image() function can be used only with single image pages.") + if not image_list: + continue else: - with open(Path(output_path).with_suffix('.' + d['ext']), "wb") as imgout: - imgout.write(d["image"]) + xref = image_list[0][0] + d = doc.extract_image(xref) + if d['cs-name'] == 'DeviceCMYK': + pix = pymupdf.Pixmap(doc, xref) + pix = pymupdf.Pixmap(pymupdf.csRGB, pix) + pix.save(output_path) + else: + with open(Path(output_path).with_suffix('.' + d['ext']), "wb") as imgout: + imgout.write(d["image"]) + except Exception as e: + # A single unextractable page must not abort the whole PDF. Skipping it + # also prevents an unpicklable PyMuPDF error from crossing the Pool + # boundary and masking the real cause with a pickling TypeError. + print("WARNING: Skipping PDF page %i: %s" % (i, e)) print("Processed page numbers %i through %i" % (seg_from, seg_to - 1))