Создать PDF из списка изображений

Есть ли практический способ создать PDF из списка файлов изображений, используя Python?

В Perl я знаю этот модуль. С его помощью я могу создать PDF всего за 3 строки:

use PDF::FromImage;
...
my $pdf = PDF::FromImage->new;
$pdf->load_images(@allPagesDir);
$pdf->write_file($bookName . '.pdf');

Мне нужно сделать что-то очень похожее на это, но в Python. Я знаю модуль pyPdf, но мне хотелось бы чего-то простого.

@Редактировать

Если вы пришли через Google, вот код:

from fpdf import FPDF
from PIL import Image
def makePdf(pdfFileName, listPages, dir = ''):
    if (dir):
        dir += "/"

    cover = Image.open(dir + str(listPages[0]) + ".jpg")
    width, height = cover.size

    pdf = FPDF(unit = "pt", format = [width, height])

    for page in listPages:
        pdf.add_page()
        pdf.image(dir + str(page) + ".jpg", 0, 0)

    pdf.output(dir + pdfFileName + ".pdf", "F")

22 ответа

Решение

Установите FPDF для Python:

pip install fpdf

Теперь вы можете использовать ту же логику:

from fpdf import FPDF
pdf = FPDF()
# imagelist is the list with all image filenames
for image in imagelist:
    pdf.add_page()
    pdf.image(image,x,y,w,h)
pdf.output("yourfile.pdf", "F")

Вы можете найти больше информации на странице учебника или официальной документации.

Лучший способ конвертировать несколько изображений в PDF, который я пробовал, это использовать PIL чисто. Это довольно просто, но мощно:

from PIL import Image

im1 = Image.open("/Users/apple/Desktop/bbd.jpg")
im2 = Image.open("/Users/apple/Desktop/bbd1.jpg")
im3 = Image.open("/Users/apple/Desktop/bbd2.jpg")
im_list = [im2,im3]

pdf1_filename = "/Users/apple/Desktop/bbd1.pdf"

im1.save(pdf1_filename, "PDF" ,resolution=100.0, save_all=True, append_images=im_list)

Просто установите save_all в True а также append_images в список изображений, которые вы хотите добавить.

Вы можете столкнуться с AttributeError: 'JpegImageFile' object has no attribute 'encoderinfo', Решение здесь - ошибка при сохранении нескольких JPEG-файлов в виде многостраничного PDF

Примечание: установите новейшие PIL Чтобы убедиться save_all Аргумент доступен для PDF.

Если вы используете Python 3, вы можете использовать модуль python img2pdf

установить его с помощью pip3 install img2pdf а затем вы можете использовать его в сценарии с помощью import img2pdf

образец кода

import os
import img2pdf

with open("output.pdf", "wb") as f:
    f.write(img2pdf.convert([i for i in os.listdir('path/to/imageDir') if i.endswith(".jpg")]))

Если ваши изображения представляют собой графики, которые вы создали с помощью matplotlib, вы можете использовать matplotlib.backends.backend_pdf.PdfPages(См. Документацию).

import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages

# generate a list with dummy plots   
figs = []
for i in [-1, 1]:
    fig = plt.figure()
    plt.plot([1, 2, 3], [i*1, i*2, i*3])
    figs.append(fig)

# gerate a multipage pdf:
with PdfPages('multipage_pdf.pdf') as pdf:
    for fig in figs:
        pdf.savefig(fig)
        plt.close()

pgmagick это GraphicsMagick(Magick++) привязка для Python.

Это оболочка Python для ImageMagick (или GraphicsMagick).

import os
from os import listdir
from os.path import isfile, join 
from pgmagick import Image

mypath = "\Images" # path to your Image directory 

for each_file in listdir(mypath):
    if isfile(join(mypath,each_file)):
        image_path = os.path.join(mypath,each_file)
        pdf_path =  os.path.join(mypath,each_file.rsplit('.', 1)[0]+'.pdf')
        img = Image(image_path)
        img.write(pdf_path)

Sample input Image:

PDF looks like this:

Инструкция по установке pgmagick для windows:

1) Загрузите предварительно скомпилированные двоичные пакеты из неофициальных двоичных файлов Windows для пакетов расширения Python (как указано на веб-странице pgmagick) и установите их.

Примечание. Попробуйте загрузить правильную версию, соответствующую вашей версии Python, установленной на вашем компьютере, и 32-битной или 64-битной.

Вы можете проверить, есть ли у вас 32-битный или 64-битный питон, просто набрав python на своем терминале и нажав Enter.

D:\>python
ActivePython 2.7.2.5 (ActiveState Software Inc.) based on
Python 2.7.2 (default, Jun 24 2011, 12:21:10) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.

Так оно и есть python version 2.7 и его 32 bit (Intel)] on win32 так что вы должны загрузить и установить pgmagick‑0.5.8.win32‑py2.7.exe,

Это следующие доступные пакеты расширения Python для pgmagick:

  • pgmagick-0.5.8.win-amd64-py2.6.exe
  • pgmagick-0.5.8.win-amd64-py2.7.exe
  • pgmagick-0.5.8.win-amd64-py3.2.exe
  • pgmagick-0.5.8.win32-py2.6.exe
  • pgmagick-0.5.8.win32-py2.7.exe
  • pgmagick-0.5.8.win32-py3.2.exe

2) Затем вы можете следовать инструкциям по установке здесь.

pip install pgmagick

А затем попробуйте импортировать его.

>>> from pgmagick import gminfo
>>> gminfo.version
'1.3.x'
>>> gminfo.library
'GraphicsMagick'
>>>

Первый pip install pillowв интерфейсе командной строки. Изображения могут быть в формате jpg или png. если у вас есть 2 или более изображений и вы хотите сделать их в 1 pdf-файле.

Код:

from PIL import Image

image1 = Image.open(r'locationOfImage1\\Image1.png')
image2 = Image.open(r'locationOfImage2\\Image2.png')
image3 = Image.open(r'locationOfImage3\\Image3.png')

im1 = image1.convert('RGB')
im2 = image2.convert('RGB')
im3 = image3.convert('RGB')

imagelist = [im2,im3]

im1.save(r'locationWherePDFWillBeSaved\\CombinedPDF.pdf',save_all=True, append_images=imagelist)
**** Convert images files to pdf file.****
from os import listdir
from fpdf import FPDF

path = "/home/bunny/images/" # get the path of images

imagelist = listdir(path) # get list of all images

pdf = FPDF('P','mm','A4') # create an A4-size pdf document 

x,y,w,h = 0,0,200,250

for image in imagelist:

    pdf.add_page()
    pdf.image(path+image,x,y,w,h)

pdf.output("images.pdf","F")

Как насчет этого??

from fpdf import FPDF
from PIL import Image
import glob
import os


# set here
image_directory = '/path/to/imageDir'
extensions = ('*.jpg','*.png','*.gif')
# set 0 if you want to fit pdf to image
# unit : pt
margin = 10

imagelist=[]
for ext in extensions:
    imagelist.extend(glob.glob(os.path.join(image_directory,ext)))

for imagePath in imagelist:
cover = Image.open(imagePath)
width, height = cover.size

pdf = FPDF(unit="pt", format=[width + 2*margin, height + 2*margin])
pdf.add_page()

pdf.image(imagePath, margin, margin)

destination = os.path.splitext(imagePath)[0]
pdf.output(destination + ".pdf", "F")

Некоторые изменения, чтобы сделать PDF из каталога, где файлы

Я взял код и внес небольшое изменение, чтобы сделать его пригодным для использования.

from fpdf import FPDF
from PIL import Image
import os # I added this and the code at the end

def makePdf(pdfFileName, listPages, dir=''):
    if (dir):
        dir += "/"

    cover = Image.open(dir + str(listPages[0]))
    width, height = cover.size

    pdf = FPDF(unit="pt", format=[width, height])

    for page in listPages:
        pdf.add_page()
        pdf.image(dir + str(page), 0, 0)

    pdf.output(dir + pdfFileName + ".pdf", "F")


# this is what I added
x = [f for f in os.listdir() if f.endswith(".jpg")]
y = len(x)

makePdf("file", x)

В моем случае нужно было конвертировать более 100 изображений в разных форматах (с альфа-каналом и без, с разными расширениями).

Перепробовал все рецепты из ответов на этот вопрос.

Pil => нельзя комбинировать с альфа-каналом и без него (необходимо конвертировать изображения)

fpdf => стек на большом количестве изображений

печать из html в gotenberg => очень долгая обработка

И моей последней попыткой был reportlab. И работает красиво и быстро. (Но иногда при большом вводе создается поврежденный PDF-файл). Вот мой код

      from PyPDF2 import PdfMerger
from reportlab.lib.pagesizes import letter
from reportlab.lib.units import inch
from reportlab.platypus import Image, PageBreak, Paragraph, SimpleDocTemplate

async def save_report_lab_story_to_pdf(file_name, story):
    doc = SimpleDocTemplate(
        file_name,
        pagesize=letter,
        rightMargin=32,
        leftMargin=32,
        topMargin=18,
        bottomMargin=18,
    )
    doc.build(story)


async def reportlab_pdf_builder(data, images):
    story = []
    width = 7.5 * inch
    height = 9 * inch

    chunk_size = 5 * 70
    pdf_chunks = []

    files_to_clean_up = []
    for trip in data['trips']:
        for invoice in trip['invoices']:
            for page in invoice['pages']:
                if trip['trip_label']:
                    story.append(Paragraph(
                        f"TRIP: {trip['trip_label']} {trip['trip_begin']} - {trip['trip_end']}"
                    ))
                else:
                    story.append(Paragraph("No trip"))

                story.append(Paragraph(
                    f"""Document number: {invoice['invoice_number']}
                        Document date: {invoice['document_date']}
                        Amount: {invoice['invoice_trip_value']} {invoice['currency_code']}
                    """
                ))
                story.append(Paragraph(" "))
                img_name = page['filename']
                img_bytes = images[page['path']]
                tmp_img_filename = f'/tmp/{uuid.uuid4()}.{img_name}'
                with open(tmp_img_filename, "wb") as tmp_img:
                    tmp_img.write(img_bytes)
                im = Image(tmp_img_filename, width, height)
                story.append(im)
                story.append(PageBreak())
                files_to_clean_up.append(tmp_img_filename)
                # 5 objects per page in story

                if len(story) >= chunk_size:
                    file_name = f"/tmp/{uuid.uuid4()}_{data['tail_number']}.pdf"
                    await save_report_lab_story_to_pdf(file_name, story)
                    story = []
                    pdf_chunks.append(file_name)

    merger = PdfMerger()
    for pdf in pdf_chunks:
        merger.append(pdf)

    res_file_name = f"/tmp/{uuid.uuid4()}_{data['tail_number']}.pdf"
    merger.write(res_file_name)
    merger.close()

Вот ответ ilovecomputer, упакованный в функцию и непосредственно используемый. Это также позволяет уменьшить размеры изображений и хорошо работает.

Код предполагает наличие папки внутри input_dir, которая содержит изображения, упорядоченные в алфавитном порядке по их имени, и выводит PDF-файл с именем папки и, возможно, строкой префикса для имени.

import os
from PIL import Image

def convert_images_to_pdf(export_dir, input_dir, folder, prefix='', quality=20):
    current_dir = os.path.join(input_dir, folder)
    image_files = os.listdir(current_dir)
    im_list = [Image.open(os.path.join(current_dir, image_file)) for image_file in image_files]

    pdf_filename = os.path.join(export_dir, prefix + folder + '.pdf')
    im_list[0].save(pdf_filename, "PDF", quality=quality, optimize=True, save_all=True, append_images=im_list[1:])

export_dir = r"D:\pdfs"
input_dir = r"D:\image_folders"
folders = os.listdir(input_dir)
[convert_images_to_pdf(export_dir, input_dir, folder, prefix='') for folder in folders];

Это не совсем новый ответ, но - при использовании img2pdf размер страницы оказался неправильным. Итак, вот что я сделал, чтобы использовать размер изображения, надеюсь, он кого-то найдет:

при условии, 1) все изображения одинакового размера, 2) размещение одного изображения на странице, 3) изображение заполняет всю страницу

from PIL import Image
import img2pdf

with open( 'output.pdf', 'wb' ) as f:
    img = Image.open( '1.jpg' )
    my_layout_fun = img2pdf.get_layout_fun(
        pagesize = ( img2pdf.px_to_pt( img.width, 96 ), img2pdf.px_to_pt( img.height, 96 ) ), # this is where image size is used; 96 is dpi value
        fit = img2pdf.FitMode.into # I didn't have to specify this, but just in case...
    )
    f.write( img2pdf.convert( [ '1.jpg', '2.jpg', '3.jpg' ], layout_fun = my_layout_fun ))

Готовое к использованию решение, которое конвертирует все PNG в текущей папке в PDF, вдохновленное ответом @ilovecomputer:

import glob, PIL.Image
L = [PIL.Image.open(f) for f in glob.glob('*.png')]
L[0].save('out.pdf', "PDF" ,resolution=100.0, save_all=True, append_images=L[1:])

Больше ничего, кроме PIL, не нужно:)

У меня была та же проблема, поэтому я создал функцию Python, чтобы объединить несколько изображений в один PDF-файл. Код (доступен на моей странице GitHub, использует reportlab, и основан на ответах по следующим ссылкам:

Вот пример того, как объединить изображения в PDF:

У нас есть папка "D:\pictures" с изображениями типов png и jpg, и мы хотим создать из них файл pdf_with_pictures.pdf и сохранить его в той же папке.

outputPdfName = "pdf_with_pictures"
pathToSavePdfTo = "D:\\pictures"
pathToPictures = "D:\\pictures"
splitType = "none"
numberOfEntitiesInOnePdf = 1
listWithImagesExtensions = ["png", "jpg"]
picturesAreInRootFolder = True
nameOfPart = "volume"

unite_pictures_into_pdf(outputPdfName, pathToSavePdfTo, pathToPictures, splitType, numberOfEntitiesInOnePdf, listWithImagesExtensions, picturesAreInRootFolder, nameOfPart)

Я знаю, что на вопрос дан ответ, но еще один способ решить эту проблему - использовать библиотеку подушек. Чтобы преобразовать весь каталог изображений:

from PIL import Image
import os


def makePdf(imageDir, SaveToDir):
     '''
        imageDir: Directory of your images
        SaveToDir: Location Directory for your pdfs
    '''
    os.chdir(imageDir)
    try:
        for j in os.listdir(os.getcwd()):
            os.chdir(imageDir)
            fname, fext = os.path.splitext(j)
            newfilename = fname + ".pdf"
            im = Image.open(fname + fext)
            if im.mode == "RGBA":
                im = im.convert("RGB")
            os.chdir(SaveToDir)
            if not os.path.exists(newfilename):
                im.save(newfilename, "PDF", resolution=100.0)
    except Exception as e:
        print(e)

imageDir = r'____' # your imagedirectory path
SaveToDir = r'____' # diretory in which you want to save the pdfs
makePdf(imageDir, SaveToDir)

Для использования его на одном изображении:

From PIL import Image
import os

filename = r"/Desktop/document/dog.png"
im = Image.open(filename)
if im.mode == "RGBA":
    im = im.convert("RGB")
new_filename = r"/Desktop/document/dog.pdf"
if not os.path.exists(new_filename):
    im.save(new_filename,"PDF",resolution=100.0)

Что сработало для меня в python 3.7 и img2pdf версии 0.4.0, так это использование чего-то похожего на код, данного Сайедом Шамихом Шаббиром, но изменение текущего рабочего каталога с использованием ОС, как предложил Стю в своем комментарии к решению Сайеда.

import os
import img2pdf

path = './path/to/folder'
os.chdir(path)
images = [i for i in os.listdir(os.getcwd()) if i.endswith(".jpg")]

for image in images:
    with open(image[:-4] + ".pdf", "wb") as f:
        f.write(img2pdf.convert(image))

Стоит упомянуть, что это решение выше сохраняет каждый файл.jpg отдельно в одном единственном PDF-файле. Если вы хотите, чтобы все ваши файлы.jpg были собраны в один.pdf, вы можете сделать:

import os
import img2pdf

path = './path/to/folder'
os.chdir(path)
images = [i for i in os.listdir(os.getcwd()) if i.endswith(".jpg")]

with open("output.pdf", "wb") as f:
    f.write(img2pdf.convert(images))

Я знаю, что это старый вопрос. В моем случае я использую Reportlab.

Размеры листа выражаются в точках, а не в пикселях, с точкой, равной 1/72 дюйма. Лист А4 состоит из 595,2 пункта в ширину и 841,8 пункта в высоту. Начало координат положения (0, 0) находится в нижнем левом углу. При создании экземпляра canvas.Canvas вы можете указать размер листов с помощью параметра page size, передав кортеж, первый элемент которого представляет ширину в точках, а второй - высоту. Метод c.showPage () сообщает ReportLab, что он уже закончил работу с текущим листом и переходит к следующему. Хотя второй лист еще не обработан (и не будет отображаться в документе, пока ничего не нарисовано), рекомендуется не забыть сделать это перед вызовом c.save (). Для вставки изображений в PDF-документ ReportLab использует библиотеку Pillow.Метод drawImage () принимает в качестве аргумента путь к изображению (поддерживает несколько форматов, таких как PNG, JPEG и GIF) и позицию (x, y) в том, который вы хотите вставить. Изображение можно уменьшить или увеличить, указав его размеры с помощью аргументов ширины и высоты.

Следующий код предоставляет имя файла pdf, список с файлами png, координаты для вставки изображений, а также размер для размещения на страницах с книжной ориентацией.

def pntopd(file, figs, x, y, wi, he):
    from reportlab.pdfgen import canvas
    from reportlab.lib.pagesizes import A4, letter, landscape, portrait
    w, h = letter
    c = canvas.Canvas(str(file), pagesize=portrait(letter))
    for png in figs:
        c.drawImage(png, x, h - y, width=wi, height=he)
        c.showPage()
    c.save()
    
    
    
from datetime import date
from pathlib import Path
ruta = "C:/SQLite"
today = date.today()
dat_dir = Path(ruta)
tit = today.strftime("%y%m%d") + '_ParameterAudit'
pdf_file = tit + ".pdf"
pdf_path = dat_dir / pdf_file
pnglist = ['C0.png', 'C4387.png', 'C9712.png', 'C9685.png', 'C4364.png']
pntopd(pdf_path, pnglist, 50, 550, 500, 500)

Лучший ответ уже существует!!! Я просто немного улучшаю ответ. Вот код:

from fpdf import FPDF
pdf = FPDF()
# imagelist is the list with all image filenames you can create using os module by iterating all the files in a folder or by specifying their name
for image in imagelist:
    pdf.add_page()
    pdf.image(image,x=0,y=0,w=210,h=297) # for A4 size because some people said that every other page is blank
pdf.output("yourfile.pdf", "F")

Для этого вам необходимо установить FPDF.

pip install FPDF

Вы можете использовать pdfme . Это самая мощная библиотека на Python для создания PDF-документов.

      from pdfme import build_pdf

...

pdf_image_list = [{"image": img} for img in images]

with open('images.pdf', 'wb') as f:
    build_pdf({"sections": [{"content": pdf_image_list}]})

Проверьте документы здесь

Этот ответ казался законным, но я не мог заставить его работать из-за ошибки «требуется байтовый объект, а не str». После прочтения документации img2pdf у меня сработало следующее:

      import img2pdf
import os

dirname = "/path/to/images"
imgs = []
for fname in os.listdir(dirname):
    if not fname.endswith(".jpg") and not fname.endswith(".png"):
        continue
    path = os.path.join(dirname, fname)
    if os.path.isdir(path):
        continue
    imgs.append(path)
with open("name.pdf","wb") as f:
    f.write(img2pdf.convert(imgs))

Если ваши изображения находятся в альбомном режиме, вы можете сделать это так.

from fpdf import FPDF
import os, sys, glob
from tqdm import tqdm

pdf = FPDF('L', 'mm', 'A4')
im_width = 1920
im_height = 1080

aspect_ratio = im_height/im_width
page_width = 297
# page_height = aspect_ratio * page_width
page_height = 200
left_margin = 0
right_margin = 0

# imagelist is the list with all image filenames
for image in tqdm(sorted(glob.glob('test_images/*.png'))):
pdf.add_page()
pdf.image(image, left_margin, right_margin, page_width, page_height)
pdf.output("mypdf.pdf", "F")
print('Conversion completed!')

Здесь page_width и page_height - это размер бумаги A4, где в альбомной ориентации ширина будет 297 мм, а высота - 210 мм; но здесь я отрегулировал высоту согласно моему изображению. ИЛИ вы можете использовать либо сохранение соотношения сторон, как я прокомментировал выше, для правильного масштабирования как ширины, так и высоты изображения.

Добавляя к ответу @ilovecomputer, если вы хотите сохранить pdf в памяти, а не на диске, вы можете сделать это:

      import io
from pdf2image import convert_from_bytes
 
pil_images = convert_from_bytes(original_pdf_bytes, dpi=100) # (OPTIONAL) do this if you're converting a normal pdf to images first and then back to only image pdf
pdf_output = io.BytesIO()
pil_images[0].save(pdf_output, "PDF", resolution=100.0, save_all=True, append_images=pil_images[1:])
pdf_bytes = pdf_output.getvalue()
Другие вопросы по тегам