RecursionError при загрузке определенных изображений - PullRequest
0 голосов
/ 11 марта 2020

Я пытаюсь создать модель с входным изображением, размер которого должен быть изменен и хеширован вместе с миниатюрой при сохранении. Тем не менее, я, кажется, создал бесконечный рекурсивный вызов. Некоторые изображения загружают, создают эскизы и генерируют хэши без проблем, но одно изображение заставляет программу бесконечно вызывать self.thumbnail.save(new_name, img_file) (последняя строка generate_thumbnail). Как я могу сохранить изображение и избежать рекурсивного вызова? Любая помощь приветствуется.

Это немного другая проблема, чем мой последний вопрос, RecursionError при попытке сохранить изображение . Ниже приведены некоторые свойства файлов изображений, которые я использовал, которые могут иметь или не иметь отношения.

Успешные свойства изображения:

  • 3888x2592 JPEG RGB
  • 1920x1280 PNG RGB

Неудачные свойства изображения:

  • 360x720 PNG RGBA

models.py

import hashlib
import os
import re

from base64 import b16encode
from functools import partial
from io import BytesIO
from PIL import Image

from django.db import models

from mtm.settings import MEDIA_ROOT

class Person(models.Model):
    prefix = models.CharField(blank=True, null=True, max_length=5)
    first_name = models.CharField(max_length=35)
    last_name = models.CharField(max_length=35)
    suffix = models.CharField(blank=True, null=True, max_length=5)
    image = models.ImageField(default=None, upload_to='people/')
    _image_hash = models.BinaryField(blank=True, null=True, default=None, max_length=16)
    thumbnail = models.ImageField(editable=False, null=True, default=None, upload_to='people/')
    _thumbnail_hash = models.BinaryField(blank=True, null=True, default=None, max_length=16)
    bio = models.TextField()
    phone = models.CharField(blank=True, null=True, max_length=10)
    email = models.EmailField(blank=True, null=True)

    def __str__(self):
        return self.full_name

    def save(self, *args, **kwargs):
        if self.image:
            self.generate_thumbnail()
            self.hash_thumbnail()
            self.resize_image()
            self.hash_image()

        self.bio = re.sub(r'(\r\n){2,}', '\r\n', self.bio)

        super().save(*args, **kwargs)

    def generate_thumbnail(self):
        img = Image.open(self.image).convert('RGB')
        width, height = img.size
        max_longest, max_shortest = 300, 250

        if not self.thumbnail and (width >= height and (width > max_longest or height > max_shortest)) or (height > width and (height > max_longest or width > max_shortest)):
            if width > height:
                if (height * max_longest/ width) > max_shortest:
                    new_height = max_shortest
                    new_width = int(width * new_height / height)
                else:
                    new_width = max_longest
                    new_height = int(height * new_width / width)
            else:
                if (width * max_longest / height) > max_shortest:
                    new_width = max_shortest
                    new_height = int(height * new_width / width)
                else:
                    new_height = max_longest
                    new_width = int(width * new_height / height)

            img = img.resize((new_width, new_height), Image.ANTIALIAS)
            img_file = BytesIO()
            img.save(img_file, 'JPEG', quality=90)

            new_name = 'thumbnail_' + self.image.name.split('.')[0] + '.jpg'
            self.thumbnail.save(new_name, img_file)

    def hash_thumbnail(self, block_size=65536):
        hasher = hashlib.md5()
        filename = MEDIA_ROOT + '/' + self.thumbnail.name

        with open(filename, 'rb') as f:
            for buf in iter(partial(f.read, block_size), b''):
                hasher.update(buf)

            if not self.thumbnail_hash or self.thumbnail_hash != hasher.hexdigest().lower():
                self._thumbnail_hash = hasher.digest()
                self.thumbnail.name = 'people/' + hasher.hexdigest().lower() + '.jpg'
                new_filename = MEDIA_ROOT + '/' + self.thumbnail.name
                os.rename(filename, new_filename)

    def resize_image(self):
        img = Image.open(self.image).convert('RGB')
        width, height = img.size
        max_longest, max_shortest = 960, 720

        if (width >= height and (width > max_longest or height > max_shortest)) or (height > width and (height > max_longest or width > max_shortest)):
            if width > height:
                if (height * max_longest/ width) > max_shortest:
                    new_height = max_shortest
                    new_width = int(width * new_height / height)
                else:
                    new_width = max_longest
                    new_height = int(height * new_width / width)
            else:
                if (width * max_longest / height) > max_shortest:
                    new_width = max_shortest
                    new_height = int(height * new_width / width)
                else:
                    new_height = max_longest
                    new_width = int(width * new_height / height)

            img = img.resize((new_width, new_height), Image.ANTIALIAS)
            img_file = BytesIO()
            img.save(img_file, 'JPEG', quality=90)

            new_name = self.image.name.split('.')[0] + '.jpg'
            self.image.save(new_name, img_file)

    def hash_image(self, block_size=65536):
        hasher = hashlib.md5()
        filename = MEDIA_ROOT + '/' + self.image.name

        with open(filename, 'rb') as f:
            for buf in iter(partial(f.read, block_size), b''):
                hasher.update(buf)

            if not self.image_hash or self.image_hash != hasher.hexdigest().lower():
                self._image_hash = hasher.digest()
                self.image.name = 'people/' + hasher.hexdigest().lower() + '.jpg'
                new_filename = MEDIA_ROOT + '/' + self.image.name
                os.rename(filename, new_filename)

    @property
    def image_hash(self):
        return str(b16encode(self._image_hash).lower(), 'utf-8') if self._image_hash else None

    @property
    def thumbnail_hash(self):
        return str(b16encode(self._thumbnail_hash).lower(), 'utf-8') if self._thumbnail_hash else None

    class Meta:
        verbose_name_plural = 'people'

Ответы [ 2 ]

1 голос
/ 11 марта 2020

На основе Django документации (https://docs.djangoproject.com/en/3.0/ref/models/fields/#django .db.models.fields.files.FieldFile.save )

FieldFile.save (имя, содержимое, save = True) Этот метод берет имя файла и содержимое файла и передает их в класс хранения для поля, а затем связывает сохраненный файл с полем модели. Если вы хотите вручную связать данные файла с экземплярами FileField в вашей модели, для сохранения этих данных файла используется метод save ().

Принимает два обязательных аргумента: имя, которое является именем файла, и содержимое который является объектом, содержащим содержимое файла. Необязательный аргумент save определяет, будет ли экземпляр модели сохранен после изменения файла, связанного с этим полем . По умолчанию установлено значение True.

Итак, если вы хотите избежать дубликатов, вы должны изменить:

self.thumbnail.save(new_name, img_file)

на:

self.thumbnail.save(new_name, img_file, save=False)

это также применимо для self.image в методе resize_image.

0 голосов
/ 11 марта 2020

Как уже упоминалось в комментариях к ответу @ soloidx, я переместил операции с изображениями в новый метод за пределами save().

    def save(self, *args, **kwargs):
        self.bio = re.sub(r'(\r\n){2,}', '\r\n', self.bio)

        super().save(*args, **kwargs)

    def image_ops(self):
        self.generate_thumbnail()
        self.hash_thumbnail()
        self.resize_image()
        self.hash_image()

Затем image_ops() вызывается в представлении непосредственно перед вызовом save(). Никаких бесконечных звонков на save()! Я все еще хотел бы знать, почему бесконечные звонки происходили только с определенными изображениями, но, возможно, некоторые загадки лучше оставить неразгаданными.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...