Я пытаюсь написать скрипт, который берет длинный список 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 файлов, и, похоже, мой объем памяти все еще контролируется.