Learn how Universal Manga Downloader converts downloaded images into optimized PDF files
Universal Manga Downloader converts downloaded manga images into PDF files using the img2pdf library with a Pillow fallback. This process handles various image formats and ensures compatibility across all platforms.
The core PDF compilation function uses img2pdf for fast, lossless conversion:
core/utils.py
def create_pdf( image_paths: List[str], output_pdf: str, log_callback: Callable[[str], None]) -> bool: """Compiles a list of image paths into a single PDF using img2pdf (if available) or Pillow.""" if not image_paths: log_callback("[WARN] No images to compile into PDF.") return False final_paths = [] try: # Step 1: Convert RGBA images to RGB for path in image_paths: try: with Image.open(path) as img: if img.mode in ("RGBA", "LA") or (img.mode == "P" and "transparency" in img.info): head, tail = os.path.split(path) new_filename = os.path.splitext(tail)[0] + "_converted.jpg" new_path = os.path.join(head, new_filename) img.convert("RGB").save(new_path, "JPEG", quality=90) final_paths.append(new_path) else: final_paths.append(path) except Exception: final_paths.append(path) # Step 2: Use img2pdf for fast conversion if img2pdf: with open(output_pdf, "wb") as f: f.write(img2pdf.convert(final_paths, rotation=img2pdf.Rotation.ifvalid)) else: raise ImportError("img2pdf not installed") # Step 3: Generate success message with relative path try: project_root = os.getcwd() pdf_root = os.path.join(project_root, PDF_FOLDER_NAME) if os.path.abspath(output_pdf).startswith(os.path.abspath(pdf_root)): logged_path = os.path.relpath(output_pdf, pdf_root) else: logged_path = os.path.basename(output_pdf) logged_path = logged_path.replace("\\", "/") except: logged_path = os.path.basename(output_pdf) log_callback(f"[SUCCESS] PDF Generated: {logged_path}") return True except Exception as e: # Fallback to Pillow method log_callback(f"[ERROR] Failed to save PDF (img2pdf): {e}") try: log_callback("[INFO] Trying alternative method (Pillow)...") images = [] for path in image_paths: try: with Image.open(path) as img: if img.mode in ("RGBA", "P"): img = img.convert("RGB") images.append(img.copy()) except: pass if images: images[0].save( output_pdf, "PDF", resolution=100.0, save_all=True, append_images=images[1:] ) return True except Exception as e2: log_callback(f"[ERROR] Alternative method failed: {e2}") return False
for path in image_paths: try: with Image.open(path) as img: # Check if image has transparency if img.mode in ("RGBA", "LA") or (img.mode == "P" and "transparency" in img.info): # Convert to RGB and save as JPEG head, tail = os.path.split(path) new_filename = os.path.splitext(tail)[0] + "_converted.jpg" new_path = os.path.join(head, new_filename) img.convert("RGB").save(new_path, "JPEG", quality=90) final_paths.append(new_path) else: # Use original image final_paths.append(path) except Exception: # If conversion fails, use original final_paths.append(path)
The conversion uses JPEG quality 90 to balance file size and image quality. This is suitable for manga images which are typically high-contrast line art.
Lossless - Images are embedded without recompressionFast - No image processing requiredSmall files - PDFs contain raw image data, not reencoded versionsPreserves quality - Original image quality is maintained
except Exception as e: log_callback(f"[ERROR] Failed to save PDF (img2pdf): {e}") try: log_callback("[INFO] Trying alternative method (Pillow)...") images = [] for path in image_paths: try: with Image.open(path) as img: if img.mode in ("RGBA", "P"): img = img.convert("RGB") images.append(img.copy()) except: pass if images: images[0].save( output_pdf, "PDF", resolution=100.0, save_all=True, append_images=images[1:] ) return True except Exception as e2: log_callback(f"[ERROR] Alternative method failed: {e2}")
The function can automatically open generated PDFs:
if open_result: if os.path.exists(output_pdf): try: os.startfile(os.path.dirname(output_pdf)) # Open folder except: pass try: os.startfile(output_pdf) # Open PDF except: pass
Platform compatibility:
Windows - os.startfile() opens files with default applications
The web interface sets core.config.OPEN_RESULT_ON_FINISH = False to disable auto-opening, since the server may run on a different machine than the browser.
import core.config# Disable auto-opening for server environmentscore.config.OPEN_RESULT_ON_FINISH = False# Use custom output directorycore.config.PDF_FOLDER_NAME = "output/manga"
Always sort images - Ensure correct page order with files.sort()Handle transparency - Convert RGBA images to RGB before PDF creationUse img2pdf first - It’s faster and produces better qualityProvide fallbacks - Have a backup method if primary failsClean up temporary files - Delete downloads after PDF creationLog all errors - Use callbacks to inform users of failuresSanitize filenames - Remove invalid characters from manga titlesUse context managers - Ensure images are properly closed