Сценарий уменьшения размера файла пакетного изображения падает после примерно 200 изображений, обработанных в Python 3 с подушкой (PIL) - PullRequest
0 голосов
/ 19 апреля 2019

Я пытаюсь написать скрипт, который берет длинный список PDF-файлов с количеством изображений, обычно от 1 до 20 изображений (файлы PDF содержат ТОЛЬКО изображения, и ничего больше). Он открывает каждый файл PDF и анализирует каждое изображение, содержащееся в нем, и пытается уменьшить размер файла каждого изображения, содержащегося в нем, до ~ 500 КБ. (Значительно заимствуя из этого ответа: Python PIL: найдите размер изображения, не записывая его в виде файла )

После такой обработки около 200 изображений появляется сообщение «Недостаточно памяти (случай 4)» и OSError: ошибка кодировщика -2 при записи файла изображения. Они генерируются при вызове функции save () объекта PIL. Сначала я подумал, что это напрямую связано с объектом BytesIO, в который он был сохранен, но я также попытался записать файл на диск с похожими результатами. Когда программа окончательно завершает работу, сценарий обычно использует ~ 1,5 ГБ ОЗУ, когда он обычно начинается с 300-500 МБ и постоянно увеличивается.

Ниже мой код; он всегда зависал в функции compress (), но это не значит, что если условия были неправильными, он не зависал при повторном сохранении PDF в функции imagemaint ():

import os
import minecart
from PIL import Image
from shutil import copyfile

def imagemaint(args):

    ## DATABASE/SQL code removed

    while True:
        q_images_fetch = q_cur.fetchmany()
        print(f'len(q_images_fetch): {len(q_images_fetch)}')
        if len(q_images_fetch) < 1:
            break

        images_proc_num = 0
        image_max_size = 500000
        backup_dest = 'D:\\New_path\\backups'
        for q_image in q_images_fetch:

            # use = False
            # if q_image[1] == 1 and q_image[3].lower() == '.jpg':
            #     use = True
            if q_image[1] == 1 and q_image[3] == '.pdf':
                pdf = True
            else:
                pdf = False

            if pdf:
                filename = f'\\\\path\\{q_image[0]}.qid'
                try:
                    filesize = os.path.getsize(filename)
                except Exception as e:
                    print('\n' + '-' * 60)
                    msg = f'Type: {type(e)}\nFile \\\\path\\{q_image[0]}.qid does not exist. ({q_image[2]})'
                    logger.warning(msg, exc_info=True)
                    print('-' * 60)
                    filesize = 0

                if filesize > 1000000:
                    num_images = 0
                    images = []
                    print(f'Filename: {q_image[2]} ({q_image[0]}.pid), size: {filesize} bytes, added date: {q_image[4]}')

                    pdffile = open(filename, 'rb')
                    doc = minecart.Document(pdffile)
                    new_size = 0
                    for page in doc.iter_pages():
                        for im in page.images:
                            num_images += 1

                    if filesize / num_images > image_max_size:
                        copyfile(filename, f'{backup_dest}/{q_image[0]}.qid')
                        for page in doc.iter_pages():
                            for im in page.images:
                                orig_image = im.as_pil()
                                cur_size = orig_image.size
                                # print(f'Orig size: {cur_size}')
                                resized_image, size = compress(orig_image, image_max_size)
                                print(f'{images_proc_num}', flush=True, end='')
                                images_proc_num += 1
                                new_size += size
                                images.append(resized_image)

                        print(f'\nTotal new file size: {new_size} bytes.')


                    images[0].save(f'D:\\New_path\\{q_image[0]}.qid', quality=70, optimize=True, save_all=True, append_images=images[1:], format='pdf')

def compress(original_image, max_size):
    save_opts={'optimize': True, 'quality':70, 'format':'jpeg'}
    width, height = original_image.size
    scales = [scale / 1000 for scale in range(1, 1001)]
    lo = 0
    hi = len(scales)

    while lo < hi:
        mid = (lo + hi) // 2

        scaled_size = (int(width * scales[mid]), int(height * scales[mid]))
        get_input = ''
        try:
            resized_file = original_image.resize(scaled_size, Image.LANCZOS)
        except Exception as e:
            print('\n' + '-' * 60)
            msg = f'Error with saving BytesIO.'
            logger.warning(msg, exc_info=True)
            print('-' * 60)
            get_input = input(f'Type Something...')

        try:
            # file_bytes = io.BytesIO()
            resized_file.save('tmp.jpg', **save_opts)
            size = os.path.getsize('tmp.jpg')
            # size = file_bytes.tell()
            # file_bytes.close()
        except Exception as e:
            print('\n' + '-' * 60)
            msg = f'Error with saving BytesIO.'
            logger.warning(msg, exc_info=True)
            print('-' * 60)
            get_input = input(f'Type something 2')

        if get_input:
            print(f'size: {size}, scales[mid]: {scales[mid]}')

        # print(size, scales[mid])
        print('.', flush=True, end='')
        if size < max_size:
            lo = mid + 1
        else:
            hi = mid

    scale = scales[max(0, lo - 1)]
    print('.', flush=True, end='')
    return [original_image.resize((int(width * scale), int(height * scale)), Image.LANCZOS), size]

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

Как написано здесь, он все еще сохраняет «пробные» изображения в фактический файл, но закомментированный код все еще там с синтаксисом BytesIO. (В качестве примечания: если кто-то знает лучший способ определить размер изображения PIL с измененным размером, кроме записи в файл или подобный ему файл, у меня все уши, так как я также не могу точно определить исходный размер файла изображения, содержащиеся в PDF-файлах, без повторного сохранения его в формате «JPEG» с качеством изображения 95 до изменения размера, что является лишь несколько точным.)

редактировать 24.04.19:

Для чего бы то ни было, я обнаружил своего рода решение, хотя оно на самом деле не решает основную причину, по которой мой код вызывал ошибку нехватки памяти - я использовал модуль gc для форсирования процессов сбора мусора:

for q_image in q_images_fetch:
    [code]
    gc.collect()

Теперь я смог обработать более 200 файлов, и, похоже, мой объем памяти все еще контролируется.

...