Convertendo centenas de imagens em alguns PDFs com Python

Se você não tem nada a ver com programação, nem tem interesse no assunto, simplesmente ignore este artigo.


Mike Driscoll tem um blog dedicado a Python chamado The Mouse vs. the Python e eventualmente há excelentes artigos por lá.

Esta é uma tradução livre do artigo Reportlab: Converting Hundreds of Images Into PDFs e modifiquei sutilmente algumas coisas, incluindo os exemplos. Agradeço ao Mike pelos excelentes artigos e me desculpo pelas liberdades que tomei na tradução (incluindo a mudança no nome do artigo). Vamos a ele!


Recentemente me pediram para converter algumas centenas de imagens em páginas de um PDF. Um amigo meu desenha comics e meu irmão queria poder lê-lo em um tablet. Acontece que se você tem um monte de arquivos nomeados assim:

'Jia_01.Jpg', 'Jia_02.Jpg', 'Jia_09.Jpg', 'Jia_10.Jpg', 'Jia_11.Jpg', 'Jia_101.Jpg'

o tablet Android vai reordená-los mais ou menos assim:

'Jia_01.Jpg', 'Jia_02.Jpg', 'Jia_09.Jpg', 'Jia_10.Jpg', 'Jia_101.Jpg', 'Jia_11.Jpg'

E se torna muito confuso ler com os arquivos fora de ordem desse jeito. Infelizmente, até mesmo o Python ordena os arquivos desse modo. Tentei usar o módulo glob diretamente e então ordenar o resultado e resultou no mesmo problema. Assim a primeira coisa que eu tinha que fazer era achar algum tipo de ordenação que organizasse os arquivos corretamente. Deve ser notado que o Windows 7 pode ordenar os arquivos corretamente no sistema de arquivos, mas o Python não.

Depois de uma pequena busca no Google, encontrei o seguinte script no StackOverflow:

import re
 
#----------------------------------------------------------------------
def sorted_nicely( l ):
    """
    Sort the given iterable in the way that humans expect.
    """
    convert = lambda text: int(text) if text.isdigit() else text
    alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ]
    return sorted(l, key = alphanum_key)

Funcionou perfeitamente! Agora eu só tinha que arrumar um jeito de colocar cada página da HQ em sua própria página dentro do arquivo PDF. Felizmente, a biblioteca reportlab torna essa tarefa muito fácil de realizar. Você só precisa percorrer as imagens e inserí-las uma a uma em uma página. É mais fácil dar uma olhada no código, assim vejamos:

import glob
import os
import re
 
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Paragraph, Image, PageBreak
from reportlab.lib.units import inch
 
#----------------------------------------------------------------------
def sorted_nicely( l ):
    """
    # http://stackoverflow.com/questions/2669059/how-to-sort-alpha-numeric-set-in-python
 
    Sort the given iterable in the way that humans expect.
    """
    convert = lambda text: int(text) if text.isdigit() else text
    alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ]
    return sorted(l, key = alphanum_key)
 
#----------------------------------------------------------------------
def create_comic(fname, front_cover, back_cover, path):
    """"""
    filename = os.path.join(path, fname + ".pdf")
    doc = SimpleDocTemplate(filename,pagesize=letter,
                            rightMargin=72,leftMargin=72,
                            topMargin=72,bottomMargin=18)
    Story=[]
    width = 7.5*inch
    height = 9.5*inch
 
    pictures = sorted_nicely(glob.glob(path + "\\%s*" % fname))
 
    Story.append(Image(front_cover, width, height))
    Story.append(PageBreak())
 
    x = 0
    page_nums = {100:'%s_101-200.pdf', 200:'%s_201-300.pdf',
                 300:'%s_301-400.pdf', 400:'%s_401-500.pdf',
                 500:'%s_end.pdf'}
    for pic in pictures:
        parts = pic.split("\\")
        p = parts[-1].split("%s" % fname)
        page_num = int(p[-1].split(".")[0])
        print "page_num => ", page_num
 
        im = Image(pic, width, height)
        Story.append(im)
        Story.append(PageBreak())
 
        if page_num in page_nums.keys():
            print "%s created" % filename
            doc.build(Story)
            filename = os.path.join(path, page_nums[page_num] % fname)
            doc = SimpleDocTemplate(filename,
                                    pagesize=letter,
                                    rightMargin=72,leftMargin=72,
                                    topMargin=72,bottomMargin=18)
            Story=[]
        print pic
        x += 1
 
    Story.append(Image(back_cover, width, height))
    doc.build(Story)
    print "%s created" % filename
 
#----------------------------------------------------------------------
if __name__ == "__main__":
    path = r"C:\Users\Mike\Desktop\Sam's Comics"
    front_cover = os.path.join(path, "FrontCover.jpg")
    back_cover = os.path.join(path, "BackCover2.jpg")
    create_comic("Jia_", front_cover, back_cover, path)

Vamos detalhar isso um pouco. Como sempre ocorre, você tem alguns imports necessários que são exigidos para que o código funcione. Você vai notar que nós também temos dentro do código a função sorted_nicely de que falamos anteriormente. A função principal se chama create_comic e recebe quatro parâmetros: fname, front_cover, back_cover, path. Se você já usou o reportlab antes, deve ter reconhecido o SimpleDocTemplate e o Story, que são mostrados no referido tutorial

De qualquer forma, você faz um laço (loop) nas imagens ordenadas e vai adicionando as imagens ao Story, sempre com um objeto PageBreak. A razão de haver um condicional dentro do laço é que eu descobri que se eu tentar construir o PDF com todas as 400+ imagens, termina ocorrendo um erro de memória. Assim eu quebrei o trabalho em uma série de documentos onde cada um tem 100 páginas ou menos. Ao final do documento, você tem que chamar o método build do objeto doc para criar o documento PDF propriamente.

Agora você sabe como eu converti um monte de imagens em alguns documentos PDF. Teoricamente, você pode usar o PyPdf para juntar todos os PDFs resultantes em um PDF único, mas eu não tentei fazer isso. Pode ser que apareça um outro erro de memória. Deixo esse teste como um exercício para o leitor.

P. S.: A foto deste post é de NobMouse.

Special: 
Avalie: 
No votes yet

Comentar