Search code examples
djangopython-imaging-library

"ValueError: not enough image data" from Pillow when saving to a Django ImageField


I have a Django model like:

class Website(models.Model):
    favicon = models.ImageField(
        null=False,
        blank=True,
        default="",
        width_field="favicon_width",
        height_field="favicon_height",
    )
    favicon_width = models.PositiveSmallIntegerField(null=True, blank=True)
    favicon_height = models.PositiveSmallIntegerField(null=True, blank=True)

I'm fetching a .ico file and saving it to the favicon field as a .png like this:

import io
import requests
from django.core import files
from .models import Website

website = Website.objects.get(pk=1)
response = requests.get("https://www.blogger.com/favicon.ico", stream=True)
image_data = io.BytesIO(response.content)
website.favicon.save("favicon.png", files.File(image_data))

Behind the scenes, Pillow successfully converts the ICO file to a PNG, and saves the file. But I then get an error. The traceback includes:

# More here

File /usr/local/lib/python3.10/site-packages/django/db/models/fields/files.py:491, in ImageField.update_dimension_fields(self, instance, force, *args, **kwargs)
    489 # file should be an instance of ImageFieldFile or should be None.
    490 if file:
--> 491     width = file.width
    492     height = file.height
    493 else:
    494     # No file, so clear dimensions fields.

# Lots more here

File /usr/local/lib/python3.10/site-packages/PIL/Image.py:804, in Image.frombytes(self, data, decoder_name, *args)
    802 if s[0] >= 0:
    803     msg = "not enough image data"
--> 804     raise ValueError(msg)
    805 if s[1] != 0:
    806     msg = "cannot decode image data"

ValueError: not enough image data

So it can save the image but, I'm guessing, not get the width/height from the image to save.

BUT, some ICO files (like https://thoughts.hnr.fyi/images/favicon.ico) work fine.


Solution

  • It took a lot of trial and error, but this seems to work:

    import io
    import requests
    from django.core.files.base import ContentFile
    from .models import Website
    
    website = Website.objects.get(pk=1)
    response = requests.get("https://www.blogger.com/favicon.ico", stream=True)
    
    img = Image.open(io.BytesIO(response.content))
    
    # Convert image to a PNG
    buffer = io.BytesIO()
    img.save(fp=buffer, format="PNG")
    
    website.favicon.save("favicon.png", ContentFile(buffer.getvalue()))